# Recipes (/en/api-reference/recipes)
# SDKs (/en/api-reference/sdks)
# Contribute an ASR Extension (/en/ai/create_asr_extension)
This page has moved to [Contribute an ASR extension](./reference/ten-agent/create-asr-extension).
# Contribute a TTS Extension (/en/ai/create_tts_extension)
This page has moved to [Contribute a TTS extension](./reference/ten-agent/create-tts-extension).
# Voice Agent overview (/en/ai)
A Voice Agent is a realtime system that listens to a user, reasons over the conversation, and responds with speech. On Agora, that experience is built from a shared foundation: realtime transport, an agent runtime, AI models, and the endpoint where the conversation is delivered.
Agora supports two Voice Agent paths: **in apps** and **on dedicated devices**. Choose **In apps** if your agent lives in a web, mobile, desktop, or backend-assisted app experience. Choose **On dedicated devices** if your endpoint is embedded or hardware-first, such as a toy, wearable, kiosk, or companion. Use the quickstart that matches your endpoint to jump directly into the right implementation path.
## Capabilities [#capabilities]
No matter where you ship it, a Voice Agent usually has the same core layers:
* **Realtime transport**: moves audio, events, and conversation state between the user and the agent with low latency.
* **Agent runtime**: manages the session lifecycle, turn taking, interruptions, memory, and tool orchestration.
* **Models**: provide ASR, LLM, TTS, and optional multimodal capabilities.
* **Endpoint**: presents the experience to the user, either through an app or a dedicated device.
The first three layers stay conceptually similar across the docs. The main split in the sidebar happens at the **endpoint** layer, because app teams and device teams need different implementation workflows.
## How this section is organized [#how-this-section-is-organized]
The AI sidebar is structured in two paths:
* **In apps**: for web, mobile, desktop, and backend-assisted app experiences.
* **On dedicated devices**: for embedded or hardware-first endpoints such as toys, wearables, kiosks, and companions.
Both paths share the same Voice Agent idea, but they differ in what teams need to build next. App teams usually care first about session orchestration, UI, model behavior, and runtime events. Device teams usually care first about bring-up, networking, firmware, controls, and hardware integration.
# Release notes (/en/ai/release-notes)
This document tracks notable changes and improvements to the Conversational AI Engine.
## Releases [#releases]
### v2.9 [#v29]
Released on July 1, 2026.
#### New features [#new-features]
Included in this release:
* **Manual turn control**
You can now explicitly control when a user's speech starts and ends by sending manual Start of Speech (SoS) and End of Speech (EoS) requests over Signaling (RTM), instead of relying on automatic detection. This supports scenarios with strict turn-boundary requirements, such as AI interviews, interactive quizzes, or push-to-talk. Set [`start_of_speech.mode`](/en/api-reference/api-ref/conversational-ai/join#properties-turn-detection-config-start-of-speech-mode) and [`end_of_speech.mode`](/en/api-reference/api-ref/conversational-ai/join#properties-turn-detection-config-end-of-speech-mode) to `manual` when starting the agent. For details, see [Manually control start and end of speech](/en/ai/best-practices/manual-turn-control).
* **Agent state notifications over RTM Message**
You can now track agent state changes in real time using Signaling (RTM) Message events. The agent pushes `state.listening`, `state.thinking`, and `state.speaking` events as individual RTM messages, making it easier to record state change timestamps, process events sequentially, or consume state events alongside other RTM Message events. For details, see [Get agent state](/en/ai/best-practices/get-agent-state).
* **Pre-recorded greeting audio**
You can now play a pre-recorded audio file as the agent's greeting instead of synthesizing it with TTS. Set [`properties.llm.greeting_audio_url`](/en/api-reference/api-ref/conversational-ai/join#properties-llm-greeting-audio-url) to the URL of an `mp3`, `wav`, or `pcm` file when starting the agent. If the audio fails to download, decode, or is in an unsupported format, the agent automatically falls back to TTS synthesis using `greeting_message`.
* **Managed mode**
You can now use supported ASR, LLM, and TTS providers with Agora-managed credentials, without supplying your own API keys. Set [`credential_mode`](/en/api-reference/api-ref/conversational-ai/join#properties-asr-credential-mode) to `"managed"` within the `asr`, `llm`, or `tts` block when starting an agent. Managed mode replaces the deprecated `preset` parameter. For details, see [Use managed mode](/en/ai/build/custom-model-integration/managed-mode).
#### Improvements [#improvements]
This release includes the following enhancements.
* **Speech captured during an uninterruptible greeting**
When [`properties.llm.greeting_configs.interruptable`](/en/api-reference/api-ref/conversational-ai/join#properties-llm-greeting-configs-interruptable) is set to `false`, the agent now silently collects the user's ASR results while the greeting plays. After the greeting ends, the system concatenates the collected speech segments into a single user message and sends it to the LLM for a unified response, instead of discarding speech that occurs during playback.
#### API changes [#api-changes]
This release introduces the following changes to the RESTful API.
* Changes to [**Start a conversational AI agent**](/en/api-reference/api-ref/conversational-ai/join)
* **New parameters**
* [`properties.llm.greeting_audio_url`](/en/api-reference/api-ref/conversational-ai/join#properties-llm-greeting-audio-url): The URL of a pre-recorded audio file (`mp3`, `wav`, or `pcm`) to play as the agent's greeting instead of synthesizing it with TTS.
* [`properties.llm.greeting_configs.audio_download_timeout_ms`](/en/api-reference/api-ref/conversational-ai/join#properties-llm-greeting-configs-audio-download-timeout-ms): The timeout in milliseconds for downloading the greeting audio file. Defaults to `1000`.
* [`properties.llm.greeting_configs.audio_pcm_sample_rate`](/en/api-reference/api-ref/conversational-ai/join#properties-llm-greeting-configs-audio-pcm-sample-rate): The sample rate in Hz of the greeting audio file when the format is PCM. Defaults to `16000`.
* [`properties.asr.credential_mode`](/en/api-reference/api-ref/conversational-ai/join#properties-asr-credential-mode), [`properties.llm.credential_mode`](/en/api-reference/api-ref/conversational-ai/join#properties-llm-credential-mode), and [`properties.tts.credential_mode`](/en/api-reference/api-ref/conversational-ai/join#properties-tts-credential-mode): Set to `"managed"` to use Agora-managed credentials for the provider without supplying your own API key.
* **Updated parameters**
* [`properties.turn_detection.config.start_of_speech.mode`](/en/api-reference/api-ref/conversational-ai/join#properties-turn-detection-config-start-of-speech-mode): Now accepts `manual`, which disables automatic start-of-speech detection so the client can explicitly signal the start of user speech over RTM.
* [`properties.turn_detection.config.end_of_speech.mode`](/en/api-reference/api-ref/conversational-ai/join#properties-turn-detection-config-end-of-speech-mode): Now accepts `manual`, which disables automatic end-of-speech detection so the client can explicitly signal the end of user speech over RTM.
* [`properties.llm.greeting_message`](/en/api-reference/api-ref/conversational-ai/join#properties-llm-greeting-message): In addition to serving as a standalone greeting, this field is now required as a fallback when `greeting_audio_url` is configured, and is used to maintain conversation context when an audio greeting is interrupted.
* **Deprecated parameters**
* [`preset`](/en/api-reference/api-ref/conversational-ai/join#preset): Deprecated in favor of setting `credential_mode` to `"managed"` within the `asr`, `llm`, or `tts` block. See [Use managed mode](/en/ai/build/custom-model-integration/managed-mode).
* Changes to [**Notification event types**](/en/ai/reference/event-types)
* **Updated events**
* [`110 agent error`](/en/ai/reference/event-types#110-agent-error): Now reports greeting audio URL errors when the greeting audio file fails to download, times out, is in an unsupported format, or fails to decode. The agent automatically falls back to TTS synthesis using `greeting_message`. When `parameters.enable_error_message` is `true`, these errors are also delivered to the client via the `onMessageError` RTM callback.
### v2.8 [#v28]
Released on June 11, 2026.
#### New features [#new-features-1]
Included in this release:
* **Session data retention control**
By default, session interaction text and audio are temporarily retained for the minimum necessary period to support service operation, agent optimization, and troubleshooting. To disable data retention for a session, set [`properties.parameters.opt_out`](/en/api-reference/api-ref/conversational-ai/join#properties-parameters-opt-out) to `true` when starting an agent.
* **RTC token expiry warning**
A new [`104 agent expire`](reference/event-types#104-agent-expire) event is triggered when the agent's RTC token is about to expire. Upon receiving this event, call the [Update agent](/en/api-reference/api-ref/conversational-ai/update) API with a new token to refresh it. The agent does not exit the channel or interrupt the session when this event is triggered.
* **New ASR provider**
* [xAI](models/asr/xai)
* **New LLM provider**
* [xAI Grok](models/llm/xai)
* **New TTS provider**
* [xAI](models/tts/xai)
#### Improvements [#improvements-1]
This release includes the following enhancements.
Important
These changes may affect your existing integration. Review each change and update your integration as needed.
* **Idle timeout behavior change**
Setting `properties.idle_timeout` to `0` no longer means the agent runs indefinitely. From this release, `0` only disables the channel idle timeout. Regardless of the `idle_timeout` value, the maximum running time for a single session is 72 hours, after which the agent automatically exits.
If your integration previously relied on `idle_timeout = 0` to keep an agent running until manually stopped, update your task scheduling and lifecycle management logic to account for the 72-hour limit.
#### API changes [#api-changes-1]
This release introduces the following changes to the RESTful API.
* Changes to [**Start a conversational AI agent**](/en/api-reference/api-ref/conversational-ai/join)
* **New parameters**
* [`properties.parameters.opt_out`](/en/api-reference/api-ref/conversational-ai/join#properties-parameters-opt-out): Set to `true` to disable data retention for the current session.
* **Updated parameters**
* [`properties.idle_timeout`](/en/api-reference/api-ref/conversational-ai/join#properties-idle-timeout): Valid range is now `0` to `259200` seconds (72 hours). Regardless of the value set, the maximum running time for a single session is 72 hours.
* Changes to [**Retrieve agent history**](/en/api-reference/api-ref/conversational-ai/history)
* **New response fields**
* [`contents[].speech_start_ms`](/en/api-reference/api-ref/conversational-ai/history#contents-speech-start-ms): Unix timestamp in milliseconds indicating when the user started speaking or the agent started TTS playback. Only returned when `llm.vendor` is `custom`.
* [`contents[].speech_end_ms`](/en/api-reference/api-ref/conversational-ai/history#contents-speech-end-ms): Unix timestamp in milliseconds indicating when the user stopped speaking, or when TTS playback completed or was interrupted. Only returned when `llm.vendor` is `custom`.
* [`contents[].speech_algorithmic_delay`](/en/api-reference/api-ref/conversational-ai/history#contents-speech-algorithmic-delay): The total delay in milliseconds introduced by audio processing algorithms after audio is captured from the user's microphone. Only returned when `llm.vendor` is `custom`, `contents[].role` is `user`, and actual voice input is present.
* Changes to [**Notification event types**](reference/event-types)
* **New events**
* [`104 agent expire`](reference/event-types#104-agent-expire): Triggered when the agent's RTC token is about to expire. Call the Update agent API with a new token upon receiving this event.
* **Updated events**
* [`102 agent left`](reference/event-types#102-agent-left): Added `Task lifetime limit exceeded` as a new exit reason when the 72-hour session limit is reached.
* [`103 agent history`](reference/event-types#103-agent-history): Added `contents[].speech_start_ms`, `contents[].speech_end_ms`, and `contents[].speech_algorithmic_delay`. Only returned when `llm.vendor` is `custom`.
* Changes to [**Retrieve a list of agents**](/en/api-reference/api-ref/conversational-ai/list)
* **Updated parameters**
* `state`: Now accepts a comma-separated list of values to filter by multiple agent states in a single request. For example, `state=0,1,2`.
### v2.7 [#v27]
Released on May 20, 2026.
#### New features [#new-features-2]
Included in this release:
* **New MLLM provider**
* [xAI Grok](models/mllm/xai)
* **New Avatar providers**
* [Generic avatar (Beta)](models/avatar/generic)
* **Greeting interruption control**
The `properties.llm.greeting_configs` object in the [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join) API now supports an `interruptable` parameter, which controls whether user speech can interrupt a greeting during playback.
#### Improvements [#improvements-2]
This release includes the following enhancements:
Important
These changes may affect your existing integration. Review each change and update your integration as needed.
* **Response status codes updated**
This release adjusts the HTTP response status codes and `reason` values returned by the RESTful API. New status codes `401`, `429`, and `500` are added. New `reason` values `ServiceNotEnabled`, `AccountSuspended`, and `ResourceAllocationFailed` are introduced. The existing `InvalidRequest` reason is replaced by `InvalidRequestBody`, `MissingRequiredField`, and `InvalidFieldValue`. For details, see [Status codes and error messages](/en/api-reference/api-ref/conversational-ai/status-codes).
If your integration relies on `reason` values for error handling, retry logic, or alerting, update your logic to handle the new values accordingly.
* **Query conversation turns API now returns paginated results**
The [Query conversation turn information](/en/api-reference/api-ref/conversational-ai/turns) API no longer returns all turns in a single response. Results are now paginated, defaulting to page 1 with up to 50 turns per page.
This affects sessions with more than 50 conversation turns. Update your integration to handle the new `total_turn_count` and `pagination` fields in the response, and retrieve subsequent pages as needed.
This release also adds the [112 turns finished](reference/event-types#112-turns-finished) notification event, which delivers conversation turn data in batches after a session ends as an alternative way to retrieve turn data.
* **`on_listening_action` default behavior change**
The default value of `on_listening_action` in the [Send a custom instruction](/en/api-reference/api-ref/conversational-ai/think) API has changed from `inject` to `interrupt`. When set to `interrupt`, the API immediately interrupts the current flow and initiates a new round of dialogue. If you rely on the previous default behavior, explicitly set `on_listening_action` to `inject` in your requests.
#### API changes [#api-changes-2]
This release introduces the following changes to the RESTful API.
* Changes to [**Start a conversational AI agent**](/en/api-reference/api-ref/conversational-ai/join)
* **New parameters**
* [`properties.llm.greeting_configs.interruptable`](/en/api-reference/api-ref/conversational-ai/join#properties-llm-greeting-configs-interruptable): Controls whether user speech can interrupt a greeting during playback.
* Changes to [**Query conversation turn information**](/en/api-reference/api-ref/conversational-ai/turns)
* **New query parameters**
Adds `page_index` and `page_size` fields to [query parameters](/en/api-reference/api-ref/conversational-ai/turns#query-parameters).
* **New response parameters**
The [response](/en/api-reference/api-ref/conversational-ai/turns#response) now includes additional top-level fields:
* `agent_id`: The unique identifier of the agent.
* `name`: The name of the agent.
* `channel`: The name of the RTC channel the agent joined.
* `total_turn_count`: The total number of dialogue turns in the session.
* `pagination`: Pagination details for the response, including `page_index`, `total_pages`, and `is_last_page`.
* Changes to [**Send a custom instruction**](/en/api-reference/api-ref/conversational-ai/think)
* **Behavior changes**
* [`on_listening_action`](/en/api-reference/api-ref/conversational-ai/think#on-listening-action): The default value has changed from `inject` to `interrupt`. When set to `interrupt`, the API immediately interrupts the current flow and initiates a new round of dialogue.
* Changes to [**Notification event types**](reference/event-types)
* **New events**
* `112 turns finished`: Triggered after a session ends, delivering conversation turn data in batches for post-session analysis and processing.
### v2.6 [#v26]
Released on April 22, 2026.
#### New features [#new-features-3]
Included in this release:
* **New TTS provider**
* [Deepgram](models/tts/deepgram)
* **New endpoint: Send a custom instruction**
Adds a new endpoint to inject a custom text instruction into the agent's current conversation pipeline. The instruction is processed as user input, enabling scenarios such as implicit instruction injection, client-side event triggering, and voice and text collaboration. For details, see [Send a custom instruction](/en/api-reference/api-ref/conversational-ai/think).
* **MLLM turn detection refactoring**
Introduces a new [`mllm.turn_detection`](/en/api-reference/api-ref/conversational-ai/join#properties-mllm-turn-detection) object for configuring turn detection when using MLLM. When defined, this overrides the top-level `turn_detection` object. Supported modes vary by vendor:
* OpenAI Realtime API: `agora_vad`, `server_vad`, `semantic_vad`
* Google Gemini Live: `agora_vad`, `server_vad`
* **Interruption control**
Adds a new top-level [`interruption`](/en/api-reference/api-ref/conversational-ai/join#properties-interruption) object for unified management of agent interruption behavior. Supports `start_of_speech` and `keywords` trigger modes, and configurable handling strategies when interruption is disabled.
#### Improvements [#improvements-3]
This release includes the following enhancements:
* **New agent state callbacks**
Adds three callbacks to the [Android](/en/api-reference/api-ref/conversational-ai/client-toolkit/android#onagentlisteningchanged), [iOS](/en/api-reference/api-ref/conversational-ai/client-toolkit/ios#onagentlisteningchanged), and [web](/en/api-reference/api-ref/conversational-ai/client-toolkit/web#econversationalaiapievents) Toolkit event handler interface:
* `onAgentListeningChanged`: Listen for changes in the agent's listening state to monitor when the agent starts or stops listening to user input.
* `onAgentThinkingChanged`: Listen for changes in the agent's thinking state to monitor when the agent starts or stops processing a request.
* `onAgentSpeakingChanged`: Listen for changes in the agent's speaking state to monitor when the agent starts or stops playing back speech.
* **Added `name` field to all [NCS event](reference/event-types) payloads**
Returns the agent name provided when starting the agent.
#### API changes [#api-changes-3]
This release introduces the following changes to the RESTful API.
* **New endpoint**
* [Send a custom instruction](/en/api-reference/api-ref/conversational-ai/think): `POST /v2/projects/{appid}/agents/{agentId}/think`
* Changes to [**Start a conversational AI agent**](/en/api-reference/api-ref/conversational-ai/join)
* **New parameters**
* [`interruption`](/en/api-reference/api-ref/conversational-ai/join#properties-interruption): Unified interruption control configuration. Supports `start_of_speech` and `keywords` trigger modes, and configurable handling strategies when interruption is disabled.
* [`greeting_configs.delay_ms`](/en/api-reference/api-ref/conversational-ai/join#properties-llm-greeting-configs-delay-ms): Specifies the delay in milliseconds before the agent plays the greeting message after the user joins the channel.
* [`llm.headers`](/en/api-reference/api-ref/conversational-ai/join#properties-llm-headers): Lets you pass custom headers in LLM requests, such as business-specific fields or tenant identifiers.
* [`mllm.enable`](/en/api-reference/api-ref/conversational-ai/join#properties-mllm-enable): Enables the MLLM module. Replaces the deprecated `advanced_features.enable_mllm`.
* [`mllm.turn_detection`](/en/api-reference/api-ref/conversational-ai/join#properties-mllm-turn-detection): Turn detection configuration for the MLLM module. When defined, overrides the top-level `turn_detection` object.
* [`mllm.turn_detection.server_vad_config.idle_timeout_ms`](/en/api-reference/api-ref/conversational-ai/join#properties-mllm-turn-detection-server-vad-config-idle-timeout-ms): Idle timeout in milliseconds for server VAD mode. Applicable to OpenAI Realtime API only.
* [`mllm.vendor`](/en/api-reference/api-ref/conversational-ai/join#properties-mllm-vendor) value `gemini`: Enables Google Gemini Live integration using the Gemini Developer API.
* **Behavior changes**
* `turn_detection` now handles SoS and EoS detection only. Interruption handling strategies have moved to the new top-level `interruption` field.
* `turn_detection.config.start_of_speech.mode` values `keywords` and `disabled` are deprecated. Use the new `interruption` field instead.
* When `turn_detection.end_of_speech.mode` is set to `semantic`, EOS detection supports English and Chinese only. For unsupported languages, the engine automatically falls back to VAD.
* **Deprecated fields**
* `advanced_features.enable_mllm`: Use `mllm.enable` instead.
* `turn_detection.config.start_of_speech.mode` value `keywords`: Use `interruption.mode = "keywords"` instead.
* `turn_detection.config.start_of_speech.mode` value `disabled`: Use `interruption.enable = false` with `interruption.disabled_config.strategy` instead.
* **Removed fields**
* `mllm.style`
* `mllm.create_response` and `mllm.interrupt_response` for OpenAI Realtime API.
* Changes to [**Notification event types**](reference/event-types)
* **New fields**
* Added `name` to all [NCS event payloads](reference/event-types). Returns the agent name provided when starting the agent.
### v2.5 [#v25]
Released on March 31, 2026.
#### New features [#new-features-4]
Included in this release:
* **Preset model configurations**
Adds a [`preset`](/en/api-reference/api-ref/conversational-ai/join#preset) parameter to the Join API. Pass a comma-separated list of preset names to apply predefined configurations for ASR, LLM, and TTS providers. When you use a preset, you do not need to provide the endpoint URL, API key, or model for the preset provider. Use the `asr`, `llm`, and `tts` fields to configure additional settings. When you use a preset, the corresponding ASR, LLM, or TTS service is provided through Agora-managed accounts. For details, see the [pricing](reference/pricing) page.
Available presets:
* ASR: `deepgram_nova_2`, `deepgram_nova_3`
* LLM: `openai_gpt_4o_mini`, `openai_gpt_4_1_mini`, `openai_gpt_5_nano`, `openai_gpt_5_mini`
* TTS: `minimax_speech_2_6_turbo`, `minimax_speech_2_8_turbo`, `openai_tts_1`
* **Pause state detection**
When enabled, the agent uses semantic understanding to detect when a user intends to pause the conversation. For example, if the user says "hold on" or "just a moment", the agent waits for further input instead of forwarding the utterance to the LLM. Controlled by the new [`pause_state_enabled`](/en/api-reference/api-ref/conversational-ai/join#properties-turn-detection-config-end-of-speech--mode-config-pause-state-enabled) parameter.
* **Conversation turn insights**
You can now query per-turn start, end, and latency metrics for completed agent sessions. See [Query conversation turn information](/en/api-reference/api-ref/conversational-ai/turns).
* **Real-time TTS parameter updates with custom LLM**
In scenarios with a custom LLM integrated, this release adds support for real-time TTS parameter updates during conversations, enabling a more natural conversational experience. This feature applies to the following use cases:
* The custom LLM detects a user request for the agent to change its voice or speaking style.
* The custom LLM identifies a user's positive emotion and adjusts TTS volume, pitch, speech rate, and other parameters in real time to better match the user's mood.
For implementation details, refer to [Real-time TTS parameter updates](build/custom-model-integration/custom-llm#update-tts-parameters-in-real-time).
* **New TTS provider**
* [Murf (Beta)](models/tts/murf)
* **New Avatar provider**
* [Anam (Beta)](models/avatar/anam)
#### Improvements [#improvements-4]
This release includes the following enhancements:
* **Migration from HeyGen API to LiveAvatar (Beta) API**
Updates the HeyGen avatar model configuration to use the [LiveAvatar (Beta)](models/avatar/heygen) API.
#### API changes [#api-changes-4]
This release introduces the following changes to the RESTful API.
* **New endpoint: Query conversation turns**
Adds a new GET endpoint to query conversation turn information for an agent session. After a conversation ends, use this endpoint to retrieve the start event, end event, and latency metrics for each turn within the session. For details, see [Query conversation turn information](/en/api-reference/api-ref/conversational-ai/turns).
* Changes to [**Start a conversational AI agent**](/en/api-reference/api-ref/conversational-ai/join)
* **New parameters added**
* [`preset`](/en/api-reference/api-ref/conversational-ai/join#preset)
* [`pause_state_enabled`](/en/api-reference/api-ref/conversational-ai/join#properties-turn-detection-config-end-of-speech--mode-config-pause-state-enabled)
* **Deprecated fields deleted**
The following deprecated fields are deleted:
* `properties.silence_timeout`
* `properties.advanced_features.enable_aivad`
* `properties.llm.silence_message`
### v2.4 [#v24]
Released on February 2, 2026.
#### New features [#new-features-5]
Included in this release:
* **MCP integration**
Adds a [`llm.mcp_servers`](/en/api-reference/api-ref/conversational-ai/join#properties-llm-mcp-servers) field to the [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join) API to connect the LLM to an MCP (Model Context Protocol) server. Set [`advanced_features.enable_tools`](/en/api-reference/api-ref/conversational-ai/join#properties-advanced-features-enable-tools) to `true` to enable tool calls. This allows the agent to call tools provided by external services to extend functionality.
* **Filler phrases**
Adds a [`properties.filler_words`](/en/api-reference/api-ref/conversational-ai/join#properties-filler-words) field to insert pre-set or LLM-generated filler phrases during conversations to fill periods of silence while waiting for LLM output. This feature smooths dialogue flow, reduces user anxiety, and makes agent speech sound more natural.
#### Improvements [#improvements-5]
This release includes the following enhancements:
* **Turn detection configuration optimization**
The `turn_detection` parameter structure has been updated to provide more flexible conversation turn detection. The new structure uses a `mode` + `config` pattern with separate Start of Speech (SoS) and End of Speech (EoS) detection configurations.
**Basic configuration**
* `mode`: Conversation turn detection mode (currently supports `default`)
* `config.speech_threshold`: Voice activity detection sensitivity
**Start of Speech (SoS) detection**
Detects when the user begins speaking. When SoS is detected while the agent is speaking, an interruption occurs.
The [`config.start_of_speech`](/en/api-reference/api-ref/conversational-ai/join#properties-turn-detection-config-start-of-speech) parameter supports three detection modes:
* **VAD mode** (`vad`): Triggered by voice activity detection. Supports configuration of interruption threshold, prefix padding, and ignore word list.
* **Keyword mode (Beta)** (`keywords`): Based on keyword triggering. The agent begins conversation after detecting a specified keyword.
* **Disabled mode** (`disabled`): Disables interruption. Supports append or ignore strategies.
**End of Speech (EoS) detection**
Detects when the user finishes speaking. When EoS is detected, the agent generates a response.
The [`config.end_of_speech`](/en/api-reference/api-ref/conversational-ai/join#properties-turn-detection-config-end-of-speech) parameter supports two detection modes:
* **VAD mode** (`vad`): Determines conversation end based on silence duration.
* **Semantic mode** (`semantic`): Determines conversation end based on semantic understanding. Supports configurable maximum wait time.
#### API changes [#api-changes-5]
This release introduces the following changes to the RESTful API.
* Changes to [**Start a conversational AI agent**](/en/api-reference/api-ref/conversational-ai/join)
* **[`turn_detection`](/en/api-reference/api-ref/conversational-ai/join#properties-turn-detection) has a revamped structure**
All previous fields under `turn_detection` have been deprecated and replaced with a new data structure.
* Deprecated
* `turn_detection.interrupt_mode`
* `turn_detection.interrupt_keywords`
* `turn_detection.interrupt_duration_ms`
* `turn_detection.prefix_padding_ms`
* `turn_detection.silence_duration_ms`
* `turn_detection.threshold`
* **AIVAD activation**
The `advanced_features.enable_aivad` field is now deprecated; to configure AIVAD activation use [`turn_detection.config.end_of_speech.mode = "semantic"`](/en/api-reference/api-ref/conversational-ai/join#properties-turn-detection-config-end-of-speech-mode).
* **New parameters added**
* [`advanced_features.enable_tools`](/en/api-reference/api-ref/conversational-ai/join#properties-advanced-features-enable-tools)
* [`llm.mcp_servers`](/en/api-reference/api-ref/conversational-ai/join#properties-llm-mcp-servers)
* [`properties.filler_words`](/en/api-reference/api-ref/conversational-ai/join#properties-filler-words)
* [`turn_detection.mode`](/en/api-reference/api-ref/conversational-ai/join#properties-turn-detection-mode)
* [`turn_detection.config`](/en/api-reference/api-ref/conversational-ai/join#properties-turn-detection-config)
### v2.3 [#v23]
Released on January 7, 2026.
#### New features [#new-features-6]
Included in this release:
* **Control LLM response interruption in custom LLM scenarios**
Adds a `metadata.interruptable` field in the first chunk of the `chat.completion.custom_metadata` object to control whether user speech can interrupt the agent's TTS output when using custom LLMs with streaming response (SSE). Use this to prevent interruptions when delivering critical information such as regulations, policies, or pricing. For more information, see [Configure LLM response interruption](build/custom-model-integration/custom-llm#configure-llm-response-interruption).
* **RTC media content encryption**
Adds an `rtc` parameter to the [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join) API for configuring RTC encryption.
* **New ASR provider**
* [Sarvam (Beta)](models/asr/sarvam)
#### Improvements [#improvements-6]
This release includes the following enhancements:
* **ElevenLabs TTS compatibility optimization**
Supports SSML parsing and forwarding to ensure ElevenLabs TTS correctly renders intended speech effects such as pauses, emphasis, and pronunciation.
* **Latency optimization best practices**
Adds a guide to [Optimize conversation latency](best-practices/optimize-latency).
* **Cloud Recording best practices**
Adds a guide to [Record conversations with Cloud Recording](best-practices/record-agent-conversation).
#### API changes [#api-changes-6]
This release introduces the following changes to the RESTful API.
* **Stop agent API is now asynchronous**
The [Stop a conversational AI agent](/en/api-reference/api-ref/conversational-ai/leave) API now responds immediately after request parameters are validated. The request is processed asynchronously after the API returns.
* Changes to [**Start a conversational AI agent**](/en/api-reference/api-ref/conversational-ai/join)
* **New parameters added**
* `properties.rtc`
### v2.2 [#v22]
Released on December 15, 2025.
#### New features [#new-features-7]
Included in this release:
* **Geofencing**
Use `geofence` configuration when you [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join) to limit which Agora servers the Conversational AI Engine can access based on geographic regions. See [Restrict agent zones](best-practices/regional-restrictions) for details.
* **Agent greeting mode**
Adds a field `llm.greeting_configs.mode` to the [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join) request to set the agent's greeting broadcast mode. The following modes are supported:
* `single_every` (Default): The agent broadcasts a greeting every time a user joins a channel where there are no other users.
* `single_first`: The agent broadcasts a greeting only when the first user joins a channel.
* **New TTS providers**
* [Sarvam (Beta)](models/tts/sarvam)
#### Improvements [#improvements-7]
This release includes the following enhancements:
* **TTS parameter update**
Adds `base_url` field to the TTS parameters for [OpenAI](models/tts/openai) and [ElevenLabs](models/tts/elevenlabs). This field specifies the endpoint URL for the TTS service.
#### API changes [#api-changes-7]
This release introduces the following changes to the RESTful API.
* Changes to [**Start a conversational AI agent**](/en/api-reference/api-ref/conversational-ai/join)
* **New parameters added**
* `properties.geofence`
* `llm.greeting_configs.mode`
### v2.1 [#v21]
Released on December 5, 2025.
#### New features [#new-features-8]
Included in this release:
* **Template variables**
This version adds the `llm.template_variables` field to the [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join) API, used to insert variables into the agent's `system_messages`, `greeting_message`, `failure_message`, and `parameters.silence_config.content` text. By configuring these variables, the Conversational AI engine automatically replaces them with the corresponding values defined in `llm.template_variables`. Template variables, combined with prompt customization and SIP outbound calling functionality, enable you to dynamically inject content to automate processes such as automatic hang-up, voicemail recognition, automatic message leaving, and call transfer.
* **Custom labels**
This version adds a `labels` field to the [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join) API, enabling agents to carry custom labels. These labels are bound to the agent and returned in the `payload` field of all message notification callbacks from the Conversational AI engine, allowing you to implement custom business logic, such as tagging activity IDs, customer groups, and business scenarios.
In SIP outbound call scenarios, you can pass a custom label in the `properties.labels` field when calling the outbound call interface to mark the call.
#### API changes [#api-changes-8]
This release introduces the following changes to the RESTful API.
* Changes to [**Start a conversational AI agent**](/en/api-reference/api-ref/conversational-ai/join)
* **New parameters added**
* `properties.labels`
* `llm.template_variables`
### v2.0 [#v20]
Released on November 15, 2025.
#### New features [#new-features-9]
Included in this release:
* **Telephony (Beta)**
This version adds an outbound calling feature that enables the conversational AI agent to initiate an outbound call to a specified number through a POST API. After the call is answered, the agent can engage in real-time dialogue with the callee. This feature supports a wide range of outbound AI call scenarios.
A set of phone number management APIs is also added for handling numbers connected to Conversational AI Engine. Refer to the API changes section for details.
Telephony pricing
The telephony feature is currently in **Beta** and is provided free of charge. Pricing terms may change upon official release.
* **Selective attention locking (Beta)**
This version adds the selective attention lock feature. Register voiceprints to enable the agent to identify specific speakers and suppress background voices and environmental noise, ensuring clearer, more focused conversations.
* **Graceful exit**
This version adds a new `farewell_config` field to [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join) API to configure the graceful exit feature. When enabled, calling the [Stop a Conversational Agent](/en/api-reference/api-ref/conversational-ai/leave) API causes the agent to enter an `IDLE` state before leaving the channel.
* **Keyword interruption mode**
This release adds a new option to the `turn_detection.interrupt_mode` field in the [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join) API. Set this field to `"keyword"` to enable keyword interruption mode.
When this mode is enabled, the agent stops its current behavior after detecting any of the keywords specified in the `turn_detection.interrupt_keywords` field.
* **Adaptive interruption mode**
This release adds a new option to the `turn_detection.interrupt_mode` field in the [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join) API. Set this field to `"adaptive"` to enable adaptive interruption mode.
When this mode is enabled, the agent dynamically increases the voice continuity threshold while speaking to reduce accidental interruptions.
* **New ASR, LLM, MLLM, and TTS providers**
* ASR
* [OpenAI (Beta)](models/asr/openai)
* [Speechmatics](models/asr/speechmatics)
* [Google (Beta)](models/asr/google)
* Amazon Transcribe (Beta)
* [AssemblyAI (Beta)](models/asr/assembly-ai)
* LLM
* [Groq](models/llm/groq)
* [Amazon Bedrock](models/llm/amazon)
* MLLM
* [Google Gemini Live](models/mllm/gemini)
* TTS
* [Rime (Beta)](models/tts/rime)
* [Fish Audio (Beta)](models/tts/fish-audio)
* [Google (Beta)](models/tts/google)
* [Amazon Polly (Beta)](models/tts/amazon)
* **New webhook notification events**
This release adds three new webhook notification event types to support metrics reporting and call-state monitoring:
* `111`: [agent metrics](reference/event-types#111-agent-metrics)
Notifies real-time performance metrics for each dialogue turn, including ASR, LLM, and TTS latency measurements.
* `201`: [inbound call state](reference/event-types#201-inbound-call-state)
Reports state changes for incoming calls, such as when a call starts, is answered, transferred, or hung up.
* `202`: [outbound call state](reference/event-types#202-outbound-call-state)
Reports state changes for outbound calls initiated by the agent, including call start, dialing, ringing, answer, and hang-up events.
#### Improvements [#improvements-8]
This release includes the following enhancements:
* **Support for avatars with MLLMs**
Added support for using avatars with MLLMs.
#### API changes [#api-changes-9]
This release introduces the following changes to the RESTful API.
* Changes to [**Start a conversational AI agent**](/en/api-reference/api-ref/conversational-ai/join)
* **New parameters added**
* `properties.parameters.farewell_config`
* `properties.advanced_features.enable_sal`
* `properties.sal`
* `properties.sal.sal_mode`
* `properties.sal.sample_urls`
* `properties.turn_detection.interrupt_mode` (supports `adaptive` and `keyword` values)
* `properties.turn_detection.interrupt_keywords`
* `properties.turn_detection.interrupt_duration_ms` (migrated from `vad.interrupt_duration_ms`)
* `properties.turn_detection.prefix_padding_ms` (migrated from `vad.prefix_padding_ms`)
* `properties.turn_detection.silence_duration_ms` (migrated from `vad.silence_duration_ms`)
* `properties.turn_detection.threshold` (migrated from `vad.threshold`)
* **Deprecated**
* The `vad` interface is deprecated. All configuration items have been moved to the `turn_detection` field.
* **New APIs**
* Telephony (Beta)
* Initiate an outbound call
* Retrieve call records
* Hang-up a call
* Retrieve call status
* Phone number management (Beta)
* Retrieve list of numbers
* Import number
* Retrieve number information
* Update number configuration
* Delete number
#### Toolkit API [#toolkit-api]
This release renames all APIs and parameters containing the word `transcription` in the client-side subtitle API to use `transcript`, as shown below:
#### Android [#android]
* `onTranscriptionUpdated` renamed to `onTranscriptUpdated`
* `TranscriptionRenderMode` renamed to `TranscriptRenderMode`
* `TranscriptionType` renamed to `TranscriptType`
* `TranscriptionStatus` renamed to `TranscriptStatus`
* `Transcription` renamed to `Transcript`
#### iOS [#ios]
* `onTranscriptionUpdated` renamed to `onTranscriptUpdated`
* `TranscriptionRenderMode` renamed to `TranscriptRenderMode`
* `TranscriptionType` renamed to `TranscriptType`
* `TranscriptionStatus` renamed to `TranscriptStatus`
* `Transcription` renamed to `Transcript`
#### Web [#web]
* `TRANSCRIPTION_UPDATED` renamed to `TRANSCRIPT_UPDATED`
### v1.7 [#v17]
Released on July 31, 2025.
#### New features [#new-features-10]
* **AI avatars**
Create visual avatar representations for your conversational agents using third-party avatar providers. AI avatars provide a visual presence during voice interactions, making conversations feel more natural and engaging. Enable AI avatars by setting `avatar.enable` to `true` and configuring the `avatar.vendor` and `avatar.params` fields when calling [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join) to create your agent.
Info
AI avatars require video streaming and incur additional charges. See [video calling pricing](/en/realtime-media/video/reference/pricing) for details.
* **Selective attention locking (Beta)**
This version introduces the selective attention locking feature, which uses voiceprint recognition technology to identify and filter out the speaker while suppressing background noise. This enhances the efficiency of conversational AI, particularly improving speech recognition accuracy. To experience this feature, contact [technical support](mailto\:support@agora.io).
* **Send picture messages (Beta)**
The toolkit now includes an API for [sending picture messages](build/send-multimodal-messages). You can send image URLs to the main model, which automatically references the image in future interactions to generate more relevant responses. A new callback is available to receive image message receipt details after successful transmission.
Info
* The picture messaging feature is currently in Beta and free for a limited time.
* Image processing depends on the capabilities of the integrated LLM. Ensure the LLM you connect to the Conversational AI Engine supports image input.
#### API changes [#api-changes-10]
This release introduces the following modifications to the RESTful API.
* [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join)
* **New parameters added**
* `avatar.enable`
* `avatar.vendor`
* `avatar.params`
##### Toolkit API [#toolkit-api-1]
* Android:
* `chat`
* `ImageMessage`
* `onMessageReceiptUpdated`
* `MessageReceipt`
* iOS:
* `chat`
* `ChatMessage`
* `ChatMessageType`
* `ImageMessage`
* `onMessageReceiptUpdated`
* `MessageReceipt`
* Web:
* `chat`
* `TMessageReceipt`
* `EChatMessagePriority`
* `EChatMessageType`
* `IChatMessageBase`
* `IChatMessageImage`
### v1.6 [#v16]
Released on July 15, 2025.
#### New features [#new-features-11]
* **Support for OpenAI realtime API**
Integrate Multimodal Large Language Models (MLLMs) with Conversational AI Engine to enable end-to-end real-time audio and text interactions. See [OpenAI Realtime API](models/mllm/openai) for integration details.
* **Support for more TTS vendors**
Conversational AI Engine now supports the following additional TTS vendors:
* [Cartesia](models/tts/cartesia)
* [OpenAI](models/tts/openai)
* **Custom ASR provider support**
To improve flexibility in configuring conversational agents, this release allows you to select a custom automatic speech recognition (ASR) provider. The [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join) API now includes the following new parameters:
* `asr.vendor`: Specify the ASR provider
* `asr.params`: Configure ASR parameters
The following ASR providers are supported:
* **ARES** (default)
* **Microsoft Azure**
* **Deepgram**
**Billing update:**
In earlier versions, the service fee included the cost of the Ares ASR provider. Starting in v1.6, the pricing is restructured as follows:
* If you use **ARES ASR**, the total price remains unchanged:
***Total cost = Conversational AI Engine Audio Basic Task + ARES ASR Task***
* If you use **a different ASR provider**, you are charged **only** the new **Conversational AI Engine Audio Basic Task** fee.
For further details, see [Pricing](reference/pricing).
* **Multi-platform toolkit**
Agora now offers a toolkit to help you quickly build conversational agent apps. The toolkit is available for [**iOS**](/en/api-reference/api-ref/conversational-ai/client-toolkit/ios), [**Android**](/en/api-reference/api-ref/conversational-ai/client-toolkit/android), and [**Web**](/en/api-reference/api-ref/conversational-ai/client-toolkit/web), and includes APIs for common scenarios. Call these APIs to combine the capabilities of the Agora Voice SDK and Signaling SDK to achieve the following functions:
* [**Display live transcript**](build/transcripts)
Display real-time text output of user-agent conversations. The transcript component is now more robust, with better error handling, session management, and extensibility.
* [**Interrupt the agent**](build/shape-the-conversation/interrupt-agent)
Stop the agent from speaking or thinking mid-conversation.
* [**Client-side events**](build/handle-runtime-events/event-notifications)
Track changes in conversation state, performance metrics, and error events.
* [**Optimize audio settings**](best-practices/audio-setup)
Quickly apply best-practice audio configurations to improve agent responsiveness and clarity.
#### API changes [#api-changes-11]
##### REST API [#rest-api]
This release introduces several important modifications to the RESTful API.
* [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join)
* **New parameters added**
* `asr.vendor`
* `asr.params`
* `advanced_features.enable_mllm`
* `properties.mllm`
* `turn_detection.type`
* `turn_detection.interrupt_duration_ms`
* `turn_detection.prefix_padding_ms`
* `turn_detection.silence_duration_ms`
* `turn_detection.threshold`
* `turn_detection.create_response`
* `turn_detection.interrupt_response`
* `turn_detection.eagerness`
* `parameters.enable_metrics`
* `parameters.data_channel`
* `parameters.enable_error_message`
##### Toolkit APIs [#toolkit-apis]
* [Android SDK](/en/api-reference/api-ref/conversational-ai/client-toolkit/android)
* [iOS SDK](/en/api-reference/api-ref/conversational-ai/client-toolkit/ios)
* [Web SDK](/en/api-reference/api-ref/conversational-ai/client-toolkit/web)
### v1.5 [#v15]
Released on June 9, 2025.
#### New features [#new-features-12]
* **Voice interruption mode**
This release adds the `turn_detection.interrupt_mode` parameter to the [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join) API, allowing you to control how the agent handles human voice interruptions. The following modes are supported:
* **`interrupt`**: (Default) The human voice immediately interrupts the agent. The agent terminates the current interaction and processes the new human voice input.
* **`append`**: The human voice does not interrupt the agent. The agent processes the newly received human voice request after the current interaction ends.
* **`ignore`**: The agent ignores human voice requests received during speaking or thinking. These requests are discarded and not stored in the context.
* **TTS filtering**
This release adds the `tts.skip_patterns` parameter to the [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join) API. This parameter controls whether the TTS module skips bracketed content when reading LLM response text. This prevents the agent from vocalizing structural prompt information like tone indicators, action descriptions, and system prompts, creating a more natural and immersive listening experience.
#### API changes [#api-changes-12]
This release introduces several important modifications to the RESTful API.
* [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join)
* **New parameters added**
* `turn_detection.interrupt_mode`
* `parameters.silence_config`
* `tts.skip_patterns`
### v1.4 [#v14]
Released on May 29, 2025.
#### New features [#new-features-13]
* **Metadata support for LLM requests**
This release adds the `llm.vendor` parameter to the [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join) API. When set to `"custom"`, the agent includes additional metadata when calling the LLM, such as `turn_id` and `timestamp`.
* **Support for Anthropic**
Conversational AI Engine now supports `anthropic` as a request style for chat completion. Refer to the `llm.style` parameter in [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join).
#### Improvements [#improvements-9]
This release includes the following enhancements:
* **Advanced LLM configuration**: The [Update agent configuration](/en/api-reference/api-ref/conversational-ai/update) API now supports:
* `llm.system_messages` for updating system prompts
* `llm.params` for modifying configuration parameters used when calling the large language model
#### API changes [#api-changes-13]
This release introduces several important modifications to the RESTful API.
* [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join)
* **New parameters added**
* `llm.vendor`
* **Removed parameters:**
* `agent_rtm_uid`
* [Update agent configuration](/en/api-reference/api-ref/conversational-ai/update)
* **New parameters added**
* `llm.system_messages`
* `llm.params`
### v1.3 [#v13]
Released on April 16, 2025.
#### New features [#new-features-14]
* **Agent conversation history**: This version adds two methods to retrieve an agent's history. The history includes messages exchanged between the user and the agent and timestamps of agent creation and exit.
* Call the RESTful API `history` endpoint to [Retrieve agent history](/en/api-reference/api-ref/conversational-ai/history).
* Subscribe to the [agent history event](reference/event-types#103-agent-history) through the [Agora message notification service](build/handle-runtime-events/webhooks). When the agent stops, Agora automatically sends the agent's history to your business server through a Webhook callback.
#### Improvements [#improvements-10]
* **Customize the priority of broadcast information**: This version upgrades the [Broadcast a message using TTS](/en/api-reference/api-ref/conversational-ai/speak) interface and adds two new configuration parameters related to broadcast interruption logic:
* `priority`: Sets the priority of the message broadcast. Supports setting the following priorities:
* `INTERRUPT` High priority
* `APPEND`: Medium priority
* `IGNORE`: Low priority
* `interruptable`: Configure whether to allow human voice to interrupt the agent's broadcast.
#### API changes [#api-changes-14]
* Adds the [Retrieve agent history](/en/api-reference/api-ref/conversational-ai/history) method.
* Adds `priority` and `interruptable` parameters to the [Broadcast a message using TTS](/en/api-reference/api-ref/conversational-ai/speak) method.
### v1.2 [#v12]
Released on April 10, 2025.
#### New features [#new-features-15]
* **Broadcast a message using TTS**: A new message broadcast interface enables a specified agent to deliver a custom message. When interacting with an agent, calling this interface interrupts the agent's speech and thinking process, allowing the TTS module to immediately broadcast the custom message.
* **Interrupt the agent**: The interrupt agent endpoint lets you stop the specified agent's speech and thinking process.
#### API changes [#api-changes-15]
This version adds the following APIs:
* [Broadcast a message using TTS](/en/api-reference/api-ref/conversational-ai/speak)
* [Interrupt the agent](/en/api-reference/api-ref/conversational-ai/interrupt)
### v1.1 [#v11]
Released on March 27, 2025.
#### New features [#new-features-16]
The [Start a conversational agent](/en/api-reference/api-ref/conversational-ai/join) API adds the `enable_rtm` and `agent_rtm_uid` parameters to enable Signaling integration with the conversational AI agent. When this feature is enabled, the agent can leverage the Signaling SDK to obtain a user's custom context information such as speaking status, selected text, signature, and score, and pass this data to the agent to generate more relevant content. For details, see [Transmit custom information](build/shape-the-conversation/custom-information).
#### Improvements [#improvements-11]
To help you quickly integrate a custom large language model (LLM), this version adds documentation for [Custom LLMs](build/custom-model-integration/custom-llm). Refer to the sample code in the documentation to integrate your custom model into the Conversational AI Engine and enable advanced capabilities such as Retrieval-Augmented Generation (RAG), multi-modal processing, and tool invocation.
#### API changes [#api-changes-16]
The `POST` method to [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join) now includes the `enable_rtm` and `agent_rtm_uid` parameters.
### v1.0 (Public Beta) [#v10-public-beta]
This version, released on March 4, 2025, adds pricing information for the Agora Conversational AI Engine. For more information, see [Pricing](reference/pricing).
#### Integration guide [#integration-guide]
To achieve the best conversation experience, use Agora Conversational AI Engine with the following Agora SDKs:
* Agora RTC Native SDK, v4.5.1 or later.
* Agora RTC Web SDK, version 4.23.2 or later.
#### New features [#new-features-17]
* **Live transcripts**: Supports real-time text output of conversations between users and the AI agent for transcript display in your app's UI. Agora provides an open-source transcript processing module. Integrate the module and call its API to implement live transcript. For details, see [Display live transcripts](build/transcripts).
* **Message Notification Service**: Introduces a new Conversational AI Engine message notification service. Configure it in the Agora console and subscribe to agent creation, stop, and error events. When a subscribed event occurs, Agora sends the details to your specified callback address. See [Receive event notifications](build/handle-runtime-events/webhooks).
* **Keywords**: Enhances recognition accuracy of Conversational AI Engine for proprietary words by adding keywords. This feature is currently in Beta stage. For details, [contact technical support](https://agoraio.zendesk.com/hc/en-us).
### v1.0 (Private Beta) [#v10-private-beta]
Released on February 18, 2025. The first beta release of the Conversational AI Engine brings natural, smooth, low-latency, and highly reliable real-time voice conversations with AI agents to Agora channels. It enables you to efficiently build intelligent and immersive interactive experiences. See [Product overview](/en/ai) for details.
#### Core features [#core-features]
* **Real-time voice conversation**
Supports natural and smooth real-time voice conversations with AI. It delivers a low-latency, ultra-responsive interactive experience as if the user is communicating with a real person.
* **Intelligent noise suppression**
Intelligently identifies and suppresses background noise, ensuring clear sound transmission even in noisy environments to provide users with a high-quality audio experience.
* **Background human voice suppression**
Suppresses background voices and noise while accurately preserving the primary speaker's voice. This ensures a clear and focused interactive experience in multi-speaker environments.
* **Intelligent interruption handling**
Allows users to interrupt AI at any time to ensure quick and natural responses. This feature enables smooth transitions and avoids mechanical interactions.
* **Intelligent transmission**
An AI-optimized transmission algorithm ensures stable voice data delivery even in weak network conditions where packet loss reaches 80%. This guarantees conversation continuity and reliability across diverse network environments.
* **Flexible arrangement**
Supports multiple Large Language Model (LLM) and Text-to-Speech (TTS) providers, enabling flexible orchestration to meet diverse business needs and deliver highly customizable AI dialogue solutions.
* **Multi-platform support**
Compatible with iOS, Android, Web, and various embedded hardware platforms, providing a seamless and consistent cross-platform experience.
#### Integration guide [#integration-guide-1]
* For the best conversational experience, Agora recommends using Conversational AI Engine with specific Agora Video/Voice SDK versions. For details, [contact technical support](mailto\:support@agora.io).
* The number of Peak Concurrent Users (PCU) allowed to call the server API under a single App ID is limited to 20. If you need to increase this limit, [contact technical support](mailto\:support@agora.io).
# About Agora (/en/introduction/about-agora)
## What Agora is [#what-agora-is]
Agora is a realtime platform for products where people, devices, services, and AI systems need to stay connected inside the same live session.
Instead of treating voice, video, messaging, AI orchestration, and media processing as separate stacks, Agora gives you a shared realtime foundation you can build on. You can start with one immediate use case, such as voice calling or an AI agent, and then extend the same session architecture into messaging, cloud processing, analytics, and production operations.
Think in live sessions, not isolated features
Agora is easiest to understand when you treat it as a shared realtime layer for interaction, coordination, AI participation, and media workflows.
## What problems it solves [#what-problems-it-solves]
Teams usually come to Agora when they need to solve more than one live-product problem at the same time:
* Keep voice and video interactions usable under real-world network conditions.
* Coordinate room state, presence, events, and messaging alongside live media.
* Add voice AI into a live session instead of bolting AI onto a disconnected backend flow.
* Record, transcribe, translate, transform, or distribute media after the session starts.
* Operate production systems with the credentials, security, monitoring, and governance live products require.
In practice, this means Agora is most useful when your product is not just sending a message or making a single API call, but managing an ongoing realtime experience.
## Product families [#product-families]
Agora's docs are easier to navigate when you group the platform into four product families instead of reading it as a list of unrelated SKUs.
Build voice agents, assistants, tutors, support flows, and multimodal AI experiences that participate in a live realtime session.
Build calling, meetings, classrooms, livestream interaction, smart-device media flows, and other low-latency communication products.
Keep users, devices, and room state synchronized with chat, presence, metadata, signaling, and collaboration surfaces.
Extend live sessions with recording, speech-to-text, translation, push and pull workflows, transcoding, and distribution tooling.
## How these products fit together [#how-these-products-fit-together]
The most useful mental model is to think in layers of responsibility:
1. **Projects and credentials** define access, billing scope, and service boundaries.
2. **Realtime sessions** connect users, devices, and agents inside a shared live context.
3. **Voice, video, and messaging** carry interaction, room state, and events through that context.
4. **AI and server-side control** add orchestration, automation, callbacks, and business logic.
5. **Cloud media services** capture, transform, translate, distribute, and observe the live session.
You do not need every layer on day one. The value of the platform is that these layers can be added over time without forcing you to replace the underlying session model.
## Common solution patterns [#common-solution-patterns]
Most implementations start from a small number of repeatable patterns:
Start with live voice or video when the main requirement is low-latency communication between participants.
Add room messaging, presence, and coordination when participants also need shared state, chat, or event-driven flows.
Add a voice agent when an AI system needs to join the session as a live participant instead of a separate backend service.
Extend a live session with archive, compliance, captions, transcripts, translation, or post-session processing.
Build smart-device or embedded experiences where microphones, speakers, sensors, and agents operate through the same realtime path.
These patterns are helpful because they let you choose a narrow first milestone while keeping a clear path to the rest of the platform.
## Where to start [#where-to-start]
Choose the next page based on the first problem you need to solve:
* Start with [Start with AI](/en/introduction/start-with-ai) if you want an AI-assisted path using Skills, MCP, and agent-friendly docs.
* Go to [Conversational AI](/en/introduction/conversational-ai) if your main product is a voice agent, assistant, tutor, or spoken AI experience.
* Go to [Real-Time Voice & Video](/en/introduction/realtime-audio-video) if your first milestone is calling, meetings, classrooms, livestream interaction, or device media transport.
* Go to [Messaging & Presence](/en/introduction/messaging-presence) if chat, presence, signaling, or shared room state is your main coordination problem.
* Go to [Cloud Media Services](/en/introduction/cloud-media-services) if you need recording, transcription, translation, media routing, or stream transformation around a live session.
* Read [Core Concepts](/en/introduction/core-concepts) if you want the platform primitives before choosing a build path.
When you move from prototype to production, continue with [Projects](/en/introduction/projects), [Console setup](/en/introduction/console-setup), [Billing](/en/introduction/billing), and [Security and privacy](/en/introduction/security-privacy).
# Account (/en/introduction/account)
This page shows you how to sign up for an Agora account, create a new project, and get the App ID and App Certificate to generate a temporary token.
## Get started with Agora [#get-started-with-agora]
To join an Agora session, you need an Agora App ID. This section shows you how to set up an Agora account, create an Agora project and get the required information from [Agora Console](https://console.agora.io/v2).
### Sign up for an Agora account [#sign-up-for-an-agora-account]
To use Agora products and services, create an Agora account with your email, phone number, or a third-party account.
Once you sign up successfully, your account is automatically logged in. Follow the on-screen instructions to create your first project and test out real-time communications.
For later visits, log in to [Agora Console](https://console.agora.io/v2) with your phone number, email address, or linked third-party account.
### Create an Agora project [#create-an-agora-project]
To create an Agora project, do the following:
1. In [Agora Console](https://console.agora.io/v2), open the [Projects](https://console.agora.io/v2/project-management) page.
2. Click **Create New**.
3. Follow the on-screen instructions to enter a project name and use case, and check **Secured mode: APP ID + Token (Recommended)** as the authentication mechanism.

4. Click **Submit**. You see the new project on the **Projects** page.
### Get the App ID [#get-the-app-id]
Agora automatically assigns a unique identifier to each project, called an App ID.
To copy this App ID, find your project on the [Projects](https://console.agora.io/v2/project-management) page in Agora Console, and click the copy icon in the **App ID** column.

## Security and authentication [#security-and-authentication]
Use the following features from your Agora account to implement security and authentication features in your apps.
### Get the App Certificate [#get-the-app-certificate]
When generating an authentication token on your app server, you need an App Certificate, in addition to the App ID.
To get an App Certificate, do the following:
1. On the [Projects](https://console.agora.io/v2/project-management) page, click the pencil icon to edit the project you want to use.

2. Click the copy icon under **Primary Certificate**.

For full App Certificate management details, see [Projects](/en/introduction/projects#manage-app-certificates).
### Generate temporary tokens [#generate-temporary-tokens]
To ensure communication security, best practice is to use tokens to authenticate the users who log in from your app.
To generate a temporary RTC token for use in your project:
1. On the [Projects](https://console.agora.io/v2/project-management) page, click the pencil icon next to your project.
2. On the **Security** panel, click **Generate Temp Token**, enter a channel name in the pop-up box and click **Generate**.
3. Copy the generated RTC token.
To generate a token for other Agora products:
1. In your browser, navigate to the [Agora token builder](https://agora-token-generator-demo.vercel.app/).
2. Choose the Agora product your user wants to log in to. Fill in **App ID** and **App Certificate** with the details of your project in Agora Console.
3. Customize the token for each user. The required fields are visible in the Agora token builder.
4. In token-based authentication, each token is specific to a user ID in your app. Generate a token for each user, and make sure the user ID in your SDK login or join call matches the user ID used to create the token.
5. Click **Generate Token**.
The token appears in Token Builder.
6. Copy the token and use it in your app.
For more information on managing other aspects of your Agora account, see [Console setup](/en/introduction/console-setup).
# Agora CLI (/en/introduction/agora-cli)
Install the Agora CLI when you want the shortest path to a working Agora starter. Use it to log in, initialize an official starter, write environment values, switch projects, and diagnose setup problems from the terminal.
If you want to get a demo running before you fine-tune prompts or architecture, start here. After the starter works, add [Agora MCP](/en/introduction/agora-mcp) or [Agora Skills](/en/introduction/agora-skills) if you want your assistant to stay grounded in current docs and Agora-specific workflows.
## Install the Agora CLI [#install-the-agora-cli]
macOS and Linux
Windows (PowerShell)
```bash
curl -fsSL https://agoraio.github.io/cli/install.sh | sh -s -- --add-to-path
agora --help
```
```powershell
irm https://agoraio.github.io/cli/install.ps1 | iex
agora --help
```
## Quick start [#quick-start]
Follow this path if you want to get an official starter running as quickly as possible.
TypeScript
Python
Go
```bash
agora login
agora init my-nextjs-demo --template nextjs
cd my-nextjs-demo
pnpm install
pnpm dev
```
```bash
agora login
agora init my-python-demo --template python
cd my-python-demo
bun install
bun run dev
```
```bash
agora login
agora init my-go-demo --template go
cd my-go-demo
make setup
make dev
```
### What happens in this flow [#what-happens-in-this-flow]
1. `agora login` authenticates your machine.
2. `agora init` clones an official starter, binds it to your Agora project, and writes the environment you need locally.
3. The runtime-specific install and dev commands start the application.
If the starter runs, move on to application-specific work. If not, troubleshoot in the same order you experienced the setup below.
## If something fails [#if-something-fails]
### The `agora` command is not available [#the-agora-command-is-not-available]
If `agora --help` does not work after installation, rerun the installer first. On macOS and Linux, make sure you used the install command with `--add-to-path`.
If PowerShell blocks the Windows install script, download `install.ps1` and run:
```text
powershell -ExecutionPolicy Bypass -File .\install.ps1
```
### Login or starter setup fails [#login-or-starter-setup-fails]
Run the setup path again in order:
```bash
agora login
agora init my-demo --template nextjs
```
This covers the two most common first-run failures: the machine is not authenticated, or the starter was not initialized correctly.
### The starter runs incorrectly [#the-starter-runs-incorrectly]
Use the CLI to check readiness, credentials, and feature enablement:
```bash
agora project doctor
```
### Project settings or credentials look wrong [#project-settings-or-credentials-look-wrong]
Inspect the current environment values that the CLI exported for your active project:
```bash
agora project env --shell
```
This is the right check when the local app starts but the agent does not join, transcripts do not appear, or the active project looks wrong.
## Build from scratch instead [#build-from-scratch-instead]
If you are not using an official starter, create or switch projects first, then export credentials into the local environment.
TypeScript
Python
Go
```bash
agora login
agora project create conv-ai-tutorial --feature rtc --feature convoai
agora project use conv-ai-tutorial
agora project env --shell
```
```bash
agora login
agora project create conv-ai-tutorial --feature rtc --feature convoai
agora project use conv-ai-tutorial
agora project env --shell
```
```bash
agora login
agora project create conv-ai-tutorial --feature rtc --feature convoai
agora project use conv-ai-tutorial
agora project env --shell
```
You can also switch the active project later or create a new one explicitly:
```bash
agora project create my-project --feature rtc --feature convoai
agora project use my-project
```
The CLI gives your assistant reproducible local steps it can execute and verify, especially during starter bootstrapping, project binding, environment generation, and first-line diagnosis.
# Agora MCP (/en/introduction/agora-mcp)
Install Agora MCP when you want your assistant to check the latest Agora docs before it writes or changes code. Use it to confirm current SDK methods, parameters, quickstarts, and product or platform differences while you build or debug.
This is most useful when documentation freshness is the risky part of the task. If you also want help choosing the right Agora workflow or starter, add [Agora Skills](/en/introduction/agora-skills).
## Install the MCP server [#install-the-mcp-server]
`Agora MCP` is included when you install [Agora Skills](/en/introduction/agora-skills), but you can also install the MCP server on its own:
```text
https://mcp.agora.io
```
Cursor
Claude Code
Codex
Gemini CLI
Manual installation
Add the following JSON to your MCP configuration:
```json
{
"mcpServers": {
"agora-docs": {
"url": "https://mcp.agora.io"
}
}
}
```
```bash
claude mcp add --transport http agora-docs https://mcp.agora.io
```
```bash
codex mcp add --url https://mcp.agora.io agora-docs
```
```bash
gemini mcp add --transport http agora-docs https://mcp.agora.io
```
Add `https://mcp.agora.io` to your MCP client and use `http` or `Streamable HTTP` transport if the client asks for a transport type.
## Start with a prompt [#start-with-a-prompt]
Install the server in your coding tool, then ask your assistant to search Agora docs before it writes code. Include the Agora product, the target platform, and the task type in the same prompt.
```text
Use Agora MCP to find the current web quickstart for Conversational AI and explain the minimum setup for a browser voice demo.
```
## Make docs lookup part of the workflow [#make-docs-lookup-part-of-the-workflow]
If your repository includes `AGENTS.md` or similar instructions, tell your assistant to prefer Agora MCP for product-specific lookups. That turns docs checks into a normal part of the workflow instead of an optional extra step.
If your MCP client is not available, these fallback resources still help:
* `https://docs.agora.io/llms.txt`
* `https://docs-md.agora.io/...` for Markdown page versions
# Agora Skills (/en/introduction/agora-skills)
Install Agora Skills when you want your assistant to choose the right Agora workflow before it starts coding. Skills helps you pick the right product path, starter, and setup sequence, so your assistant can follow official Agora patterns instead of guessing.
Use Skills when docs lookup alone is not enough and the harder part is deciding what to build first or which official path to follow. If you also want fresh documentation while your assistant works, pair Skills with [Agora MCP](/en/introduction/agora-mcp).
## Install Agora Skills [#install-agora-skills]
Skills CLI
Manual installation
```bash
npx skills add AgoraIO/skills
```
Skills activate automatically when the agent detects a relevant Agora task.
Clone the repository and point your coding assistant to the Agora skill files:
```bash
git clone https://github.com/AgoraIO/skills.git ~/agora-skills
```
Use `skills/agora/` as the directory entry point, or load `SKILL.md` directly if your tool expects a single root file.
If you install Skills manually, place the files where your assistant already looks for local instructions:
Claude Code
Cursor
Windsurf
GitHub Copilot
Other tools
Run the following commands inside Claude Code:
* **User-level** (available across all your projects)
```bash
/plugin marketplace add AgoraIO/skills
/plugin install agora
```
* **Project-level** (shared with your team via version control)
```bash
/plugin marketplace add AgoraIO/skills
/plugin install agora --scope project
```
Copy or symlink `skills/agora/` into `.cursor/rules/`.
Add `skills/agora/` to Cascade context.
Reference the files with `@workspace` or add them to Copilot instructions.
Point the tool at `skills/agora/` or the top-level `SKILL.md`.
## Start with a prompt [#start-with-a-prompt]
Install Skills, then ask your assistant to choose the correct Agora path before it starts scaffolding or editing code.
```text
Use Agora Skills and Agora MCP to help me build a web-based conversational AI demo.
Check the official docs first, choose the right starter, and use the Agora CLI for setup.
```
From there, let your assistant choose the starter and setup path before it edits code.
# Cloud Media Services (/en/introduction/cloud-media-services)
Cloud Media Services is the capability layer for products that need to do more with live media than simply transport it between participants. This category covers the cloud-side workflows that capture, transform, route, translate, and extend live sessions.
Agora's product map for this layer includes:
* **Recording** in cloud or local-server form
* **Media Push / Pull** and RTMP Gateway workflows
* **Speech-to-Text / Translation**
* **Transcoding / Compositing**
* **Extensions / Analytics** and adjacent cloud-side operational services
## What this category is good for [#what-this-category-is-good-for]
This category is the right entry point when you need:
* recording for archive, replay, compliance, or review
* pushing or pulling media between Agora and external streaming systems
* speech transcription or live translation as a cloud-side service
* stream composition, transcoding, and output shaping
* analytics, extensions, and post-session media workflows
The shared requirement is that the media session must continue to create value after, around, or beyond the primary live interaction.
## Capability map [#capability-map]
Capture live sessions into replayable and reviewable assets through cloud-managed or self-managed recording paths.
Move media between Agora and external live-streaming or online media systems through push, pull, and gateway workflows.
Turn live speech into text and translated text for accessibility, AI preprocessing, records, and multilingual products.
Reshape streams into layouts, output formats, and distribution-friendly media workflows for playback and broadcast.
Extend product behavior and add production observation, diagnosis, and ecosystem integrations around the media path.
## Start here [#start-here]
## Key characteristics [#key-characteristics]
Handle what happens to live media before, during, and after the user-facing session.
Connect live interaction with storage, broadcast delivery, analytics, moderation, and external streaming workflows.
Work with recordings, transformed streams, transcripts, translations, and playback-oriented outputs as one service layer.
Add capabilities that teams usually need once a live experience moves beyond prototype stage.
Reuse the same cloud media services whether the upstream source is RTC, AI interaction, or host-audience live streaming.
## What to read next [#what-to-read-next]
* [Media processing and distribution](/en/realtime-media/media-processing-and-distribution)
* [Cloud Recording](/en/realtime-media/cloud-recording)
* [RTMP Gateway](/en/realtime-media/rtmp-gateway)
* [Transcoding](/en/realtime-media/transcoding)
* [Realtime Transcription and Translation](/en/realtime-media/speech-to-text)
# Community Resources (/en/introduction/community-resources)
Agora Console is the unified portal for configuring, purchasing, and managing Agora products and services.
## Dashboard overview [#dashboard-overview]
After logging in to Console, the overview dashboard provides quick access to:
* projects
* billing
* usage
* members and roles
* documentation
* code samples
* support
* Agora Analytics
* Extensions Marketplace
## Additional resources [#additional-resources]
* [Support](/en/introduction/support)
* [Release Notes](/en/ai/release-notes)
* [Usage Analytics](/en/introduction/usage-analytics)
* [AI best practices](/en/ai/best-practices)
* [AI product map](/en/ai/domain-overview)
## Status and operations [#status-and-operations]
Use the Agora Status Page and the Support Center to monitor service health, track incidents, and follow maintenance events.
# Console Setup (/en/introduction/console-setup)
[Agora Console](https://console.agora.io/v2) is the unified portal for you to configure, purchase, and manage Agora products and services. This page shows you how to use the new **beta** version of Agora Console to manage all aspects of your Agora account.
## Dashboard overview [#dashboard-overview]
After logging in to Agora Console, you see the overview dashboard. The dashboard displays a summary of important statistics and provides quick access to projects, billing, documentation, and code samples.

Agora Console provides access to the following:
* Create and manage projects
Create a project, and get the App ID and App Certificate to generate a temporary token. See [Projects](/en/introduction/projects).
* Enable and configure features
Configure CHAT, Signaling, WHITE, CREC, MPUSH, MPULL, Notifications, FC, Cloud proxy, and Co-Host Authentication.
* Check usage
Check the usage amount generated by each project or all of them. See [Usage Analytics](/en/introduction/usage-analytics).
* Manage members and roles
Add members to your account, and add them to different teams with specified permissions. See [Members & Roles](/en/introduction/members-roles).
* Manage billing
Add money to your account, view your transactions, and make withdrawal requests. See [Billing center](#billing-center).
* Submit a ticket
If you have any questions about Agora products, [submit a ticket](#submit-a-ticket).
* Generate Customer ID and Customer Secret
To use Agora RESTful APIs, you need a set of Customer ID and Customer Secret.
* Agora Analytics
Track and analyze the usage and quality data of real-time communication products through highly visualized reports in order to spot issues and find root causes. For more information, see the [Agora Analytics overview](/en/realtime-media/agora-analytics/product-overview).
* Agora Extensions Marketplace
Gain access to extensions that rapidly add fun features to your app on top of Agora SDKs. For more information, see the [Extensions Marketplace overview](/en/realtime-media/marketplace).
## Manage profile [#manage-profile]
This section shows how to view and edit the profile of your Agora account.
### Open the profile page [#open-the-profile-page]
To open the **Profile** page, follow these steps:
1. Log in to [Agora Console](https://console.agora.io/v2).
2. Click your account name in the top right corner, and select your account name from the dropdown menu.
On the **Profile** page, you can view and edit your phone number, email address, password, and login methods.

### Change phone number, email address, or password [#change-phone-number-email-address-or-password]
1. Find **Phone Number**, **Email**, or **Password** fields on the profile page, then click **Update**.
2. Follow the on-screen instructions to complete.
### Link a third-party account [#link-a-third-party-account]
1. On the profile page, find **GitHub**, **WeChat**, or **Google**, then click **Connect**.
2. Follow the on-screen instructions to complete.
### Reset your password [#reset-your-password]
If you forget your password, follow these steps to reset it:
1. Go to the [login page](https://sso.agora.io/login/) and click **Forgot password**.
2. Fill in your email address and click **Send email**. Agora sends an email to you.
3. Follow the instructions in the email to reset your password.
## Create and manage projects [#create-and-manage-projects]
This section shows how to create a project, view project information, and manage App Certificates in Agora Console.
### Restrictions [#restrictions]
* If your account has multiple members, only those assigned to the teams of **Admin**, **Engineer**, or an authorized custom team have access to the **Projects** page.
* Each Agora account can create up to 20 projects. If you need to create more projects, contact Agora by [submitting a ticket](https://agora-ticket.agora.io/).
### Create an Agora project [#create-an-agora-project]
To create a project, do the following:
1. Open the [Projects](https://console.agora.io/v2/project-management) page.
2. Click **Create New**.
3. Follow the on-screen instructions to enter a project name and use case, and check **Secured mode: APP ID + Token (Recommended)** as the authentication mechanism.
4. Click **Submit**. You can now see the project on the **Projects** page.
### View project information [#view-project-information]
For a project listed on the **Projects** page, you can do the following:
* View basic information, such as the last updated and created dates, name, and security status.
* Copy the [App ID](/en/introduction/glossary#app-id) of the project.
* Click the pencil icon to open the project details page, where you can configure **Project Name**, copy **App Certificate** and **App ID**, and generate a **Temporary Token**.
### Manage app certificates [#manage-app-certificates]
An App Certificate is a string generated by Agora Console to enable token authentication. For different security requirements, Agora provides the following app certificate options:
* Primary Certificate: This certificate is used to generate tokens for temporary use or production environments.
* Secondary Certificate: This certificate is used to generate tokens for production environments only and does not apply to RESTful APIs.
### Enable the Primary Certificate [#enable-the-primary-certificate]
For high-security use-cases, enable the Primary Certificate as follows:
* If you choose **APP ID + Token** as the authentication mechanism when creating a project, the Primary Certificate is enabled by default. On the project details page, you can click the copy icon under **Primary Certificate** to view and copy it. In this case, you need to use the token generated by the **Primary Certificate** for authentication.
* If you choose **APP ID** as the authentication mechanism when creating a project, you need to enable the Primary Certificate manually. On the project detail page, click **Enable** under **Primary Certificate**.

Once the Primary Certificate is enabled, you can click the copy icon to view and copy it. In this case, you can either complete authentication with the App ID only or use tokens generated by the **Primary Certificate** for authentication.
### Enable the Secondary Certificate [#enable-the-secondary-certificate]
If you need to change the Primary Certificate after enabling it, you can enable the Secondary Certificate.
On the project detail page, click **Add a Certificate**.

The **Secondary Certificate** is now enabled.

Once the Secondary Certificate is enabled, you can click the copy icon to view and copy it. In this case, both the Primary and Secondary Certificate can be used to generate tokens for authentication.
### Switch to a new Primary Certificate [#switch-to-a-new-primary-certificate]
If you suspect that your primary certificate has been compromised, swap the primary certificate with the secondary certificate and delete the original primary certificate to avoid risks. Replacing the primary certificate may impact your online business. Best practice is to replace the primary certificate only when necessary. If you replace the primary certificate but do not delete the secondary certificate (the original primary certificate), the tokens previously generated using this certificate can still be used.
To switch to a new Certificate, follow these steps:
1. [Enable the Secondary Certificate](#enable-the-secondary-certificate).
2. On the project detail page, click the arrows icon between the Certificates to swap them.
3. Turn off the switch next to the **Secondary Certificate** to disable it.

4. Follow the on-screen instructions to complete verification. If verified successfully, the status of the Secondary Certificate is updated to **Disabled**, and the **Delete** icon appears.

5. Click the **Delete** icon.
6. Follow the on-screen instructions to complete verification. If verified successfully, the **Delete** button disappears, and the current Secondary Certificate (the original Primary Certificate) is deleted.
Once a certificate is deleted, it cannot be restored. Before deleting the secondary certificate, make sure that most users have switched to the new certificate to avoid the following consequences:
* All tokens generated using the deleted certificate become invalid, and users can no longer use these tokens to join channels.
* Users who have joined the channel cannot specify new tokens through `renewToken`.
## Check usage [#check-usage]
This section explains how to check your usage of Agora products in Agora Console, such as Interactive Live Streaming, Broadcast Streaming, Media Push, On-Premise Recording, Cloud Recording, and Interactive Whiteboard.
### Restrictions [#restrictions-1]
* If your account has multiple members, only those assigned to the team of **Admin**, **Product/Operation**, or an authorized custom team have access to the **Usage** page.
* You can only view the usage data for the past 12 months.
* Data on the **Usage** page can differ from the actual amount. For the most accurate usage data, see the bills sent to your account.
### Procedure [#procedure]
Follow these steps to check your usage:
1. Enter the [Usage](https://console.agora.io/v2/usage) page in Agora Console.
2. (Optional) Add filters for the usage data, as follows:
* Select a time frame and data granularity.
* Select **All Projects** or a specific project from the dropdown menu.
* Select a product to view its usage data.
* Check **View Breakdown**.

## Manage members and teams [#manage-members-and-teams]
Once you add members to your Agora account, you are assigned to the team of **Admin** and can add these members to different teams with specified permissions.
### Add a member [#add-a-member]
Follow these steps to add a member to your account:
1. Log in to [Agora Console](https://console.agora.io/v2), click your account name in the top-right corner, and click **Settings** in the dropdown menu.
2. In the left navigation panel, click **Teams and members**, then select the **Members** tab.
3. Click the **Add New Member** button, fill in the email address of the new member, and choose a team from the dropdown menu. Then click **OK**.

4. Agora sends a confirmation email to this address. The new member should follow the instructions in the email to finish joining the project.
### Manage members [#manage-members]
On the [Teams and members](https://console.agora.io/v2/teams-members) page, **Members** tab, you can do the following:
* Click the pencil icon to change the team that a member belongs to.
* Delete a member.
Information
Only the main account can delete a member account. Member accounts assigned to the Admin team cannot delete a member account.
### Manage teams and permissions [#manage-teams-and-permissions]
On the **Teams and members** page, **Teams** tab, you can view the permissions assigned to teams. Agora predefines five teams for you. The permissions of each predefined team are as follows:
* **Admin** has full access to all projects. The administrator can view usage data and finance information, manage members and teams, manage projects, and view Agora Analytics reports.
* **Finance** can view finance information of all projects.
* **Product/Operation** can view usage data of all projects.
* **CS/Maintenance** can view Agora Analytics reports of all projects.
* **Engineer** can manage projects and view Agora Analytics reports of all projects.
### Add a custom team [#add-a-custom-team]
Follow these steps to add a custom team:
1. On the **Teams and Members** page, **Teams** tab, click **Add New Team**.

2. Fill in the team name, and select the permissions of this team in the **Usage**, **Finance**, **Teams and Members**, **Project**, **Agora Analytics**, and **Data** columns.
3. Click **OK**.
## Integrate with Okta [#integrate-with-okta]
If you are using [Okta](https://www.okta.com/) as the identity provider for your app, you can configure Agora Console so that your team members can log into Agora using their Okta accounts. You will need:
* An Okta developer account with admin privileges
* An Agora account assigned to the **Admin** team
* A Premium or Enterprise Agora Support package
### Configuration steps [#configuration-steps]
1. Apply for Okta integration in Agora Console.
Log in to your Agora account. On the [SSO Management](https://console.agora.io/settings/sso-management) page, click **Request**.

Once you apply, a request is sent to Agora to enable integration for you.

When your application is processed by Agora, **SAML Configuration** on the same page is automatically enabled and a set of **SSO URL** and **Audience URI** is generated.
2. Create a SAML integration for Agora in Okta Console.
1. Log in to your Okta developer account as a user with administrative privileges and click **Admin** in the upper right corner.

2. Go to **Applications** > **Applications** and click **Create App Integration**.

3. In the **Sign-in method** section, select **SAML 2.0** and click **Next**.
4. On the **General Settings** tab, enter a name for this integration, for example, `Agora`, and click **Next**.
5. On the **Configure SAML** tab, fill in the following SAML configurations generated in Agora Console:
* In the **Single sign on URL** field, enter `https://sso2.agora.io/api/v0/saml/idp/callback`.
* In the **Audience URI (SP Entity ID)** field, enter `https://sso2.agora.io/{companyId}/saml/SSO` where `{companyId}` is your company ID.
* In the **Attribute Statements (optional)** section, add the following attribute:
| Name | Value |
| :------ | :----------- |
| `email` | `user.email` |

6. On the **Feedback** tab, select **I'm an Okta customer adding an internal app** and tick **This is an internal app that we have created**, then click **Finish**.

3. View IdP information in Okta Console
1. In Okta Console, select the **Sign On** tab for Agora integration and click **View Setup Instructions**. You see the following information:
* **Identity Provider Single Sign-On URL**: At the end of this procedure, your team members use this URL to sign in to Agora Console
* **Identity Provider Issuer**
* **X.509 Certificate**

4. Configure SAML settings in Agora Console
In the **SAML Configuration** section on the [SSO Management](https://console.agora.io/settings/sso-management) page in Agora Console, enter the following information from Okta Console:
* **Identity Provider Single Sign-On URL**
* **Identity Provider Issuer**
* **X.509 Certificate**, including the `BEGIN CERTIFICATE` and `END CERTIFICATE lines`.
Click **Save**.
5. Manage access
You can manage access for your team members manually using SAML, which is enabled when you apply for Okta
integration in Agora Console, or automatically, if you additionally enable SCIM. Manual management means that adding, managing, and removing a team member's access must be done in both, Okta and Agora Console. Automatic management means that SCIM automatically adds, manages, and removes members' access to Agora Console when you make changes in Okta.
**Note**: Once enabled, SAML is applicable for both, existing and newly created Agora accounts for team members. For new accounts, however, log in as the admin in Agora Console and create an account *before* it is configured in Okta. SCIM is applicable only for new accounts.
* Manual management with SAML
1. In Agora Console, add team members and choose their teams. For details, see [Manage members and teams](#manage-members-and-teams).
2. In Okta Console, go to **Directory** > **People**. Ensure that the email address of each team member is the same as that in Agora Console.
3. Go to **Applications** > **Applications** > **Agora integration**.
4. On the **Assignments** tab, click **Assign** and select **Assign to People**.
5. Enter the team members that need to sign in to Agora Console and click **Assign** for each. Click **Done**.
* Automated management with SCIM
1. In Agora Console, enable **SCIM API Basic Auth** on the **SSO Management** page. A set of username and password is generated, along with the SCIM connector base URL.

2. In Okta Console, select the **General** tab under **Agora integration** and click **Edit**.
3. In the **Provisioning** section, select **SCIM** and click **Save**.
4. Select the **Provisioning** tab under **Agora integration**, then under **Settings** > **Integration** click **Edit**. Make the following changes:
* In **SCIM connector base URL**, enter the corresponding URL from Agora Console.
* In **Unique identifier field for users**, enter `email`.
* Under **Supported provisioning actions**, tick all the checkboxes.
* In **Authentication Mode**, select **Basic Auth**.
* Under the **Basic Auth** section, enter the username and password from the SSO management page in Agora Console.

After enabling and configuring Okta integration, the [Big Bang feature](https://help.okta.com/oie/en-us/content/topics/reference/glossary.htm) is enabled by default. This means that login
to Agora Console with email and password is no longer available and your team members can log in only through Okta.
## Billing center [#billing-center]
This section shows how to check your account balance, add money to your account, view transactions, and make a withdrawal in Agora Console.
### Restrictions [#restrictions-2]
If your account has multiple members, only those assigned to the teams of **Admin**, **Finance**, or an authorized custom team have access to the **Billing** page.
### Check account balance [#check-account-balance]
After logging in to [Agora Console](https://console.agora.io/v2), you can see your account balance in the **Billing** panel on the **Overview** page.
### Add money to account [#add-money-to-account]
You can add money to your account either with a credit card or via bank transfer.
### Credit card [#credit-card]
To add money to your account with a credit card, follow these steps:
1. In [Billing](https://console.agora.io/v2/billing), select the **Credit Card** tab.
2. (Optional) If you have not added a credit card to your account, follow these steps:
1. Click **Add New Card**.

2. Enter the card number, card holder name, expiration date, and CVC.
3. Choose whether or not to set this card as the default credit card.
4. Click **Continue**.
3. Select a credit card, enter the **Payment Amount**, and click **Pay**. If the payment is successful, you see a "Payment Succeeded" message.
### Bank transfer [#bank-transfer]
To add money to your account via bank transfer, follow these steps:
1. In [Billing](https://console.agora.io/v2/billing), select the **Bank Transfer** tab.

2. Follow the on-screen instructions to complete.
### View invoices and transactions [#view-invoices-and-transactions]
In [Billing](https://console.agora.io/v2/billing), scroll down to see **Your Invoices** and **Your Transactions**, respectively.
You can also click **View all** > **Export CSV** in the top right corner to export transactions and invoices as a CSV file.
## Submit a ticket [#submit-a-ticket]
To ask Agora support a question, follow these steps:
1. Log in to [Agora Console](https://console.agora.io/v2).
2. Click **Get Support** > **Create Support Ticket**

3. Type in your question or keywords to see if the question has been answered. If you cannot find your answer, select a category and submit a ticket to Agora’s customer support.

You can track the status of your ticket under **Tickets**.
## Delete your Agora account [#delete-your-agora-account]
This section describes how to delete your Agora account created with an email address or phone number.
Before deleting your Agora account, ensure that the following requirements are met:
* You are the creator of the account to be deleted.
* The account to be deleted is an Agora account created in [Agora Console](https://console.agora.io/v2), not a reseller account created in [Agora Reseller Console](https://reseller.agora.io/).
* There are no active projects under your account. If there are any, go to the [Projects](https://console.agora.io/v2/project-management) page to disable all active projects.
* Your account balance is zero. If your account balance is positive, request a withdrawal. If your account balance is negative, add money to your account.
* Your bill for the previous month has been issued.
* No usage is generated in the current month.
* All of your packages have either expired or been used up, including:
* Video SDK and Cloud Recording packages.
* Support packages.
* Extensions Marketplace packages.
* There are no members under your account. If there are any, go to the [Teams and Members](https://console.agora.io/v2/teams-members) page to delete all members.
### Procedure [#procedure-1]
Follow these steps to delete your Agora account:
1. Log in to [Agora Console](https://dashboard.agora.io/), click your account name in the top right corner and select **Settings** in the dropdown.
2. In the left navigation panel, click **Account**.

3. Ensure that you meet all the prerequisites, and click **Delete Account** at the bottom of the page.
4. Read the pop-up prompt carefully and fill in the confirmation field. Click **Delete Account**.
If any prerequisite is not met, an error message appears after you click **Delete Account**. Check the requirements above and try again.
# Conversational AI (/en/introduction/conversational-ai)
Conversational AI is the capability layer for products where the primary interface is a live conversation with an AI system. In this category, voice is not just an input channel. It is the product experience itself.
Agora groups several related surfaces under this layer:
* **Conversational AI Engine** for production voice-agent workflows
* **AI developer toolkits** for client and server integration
* **OpenAI Realtime integration** for teams that want to combine Agora transport with OpenAI Realtime-style model interaction
## What this category is good for [#what-this-category-is-good-for]
This category is the right entry point when you need:
* voice-first AI assistants and copilots
* customer service and sales agents
* tutors, role-play systems, and spoken learning experiences
* AI-powered smart devices and hardware endpoints
* multimodal products where an agent participates in a live session
The common requirement is natural turn-taking, low response latency, interruption handling, and a live connection between the user and the AI system.
## Products and paths in this category [#products-and-paths-in-this-category]
The main product path for building realtime voice agents with managed speech, reasoning, interruption handling, and agent lifecycle control.
Client and server integration surfaces for web, mobile, and backend teams that need to build custom AI experiences on top of Agora.
A guided path for teams that want to connect Agora realtime transport with OpenAI Realtime-style voice interaction flows.
## Start here [#start-here]
## Key characteristics [#key-characteristics]
Keep spoken interaction responsive enough for natural back-and-forth conversation rather than turn-based chatbot behavior.
Combine realtime audio transport with ASR, LLM, TTS, interruption handling, and agent lifecycle control in one interaction model.
Start from Agora-managed paths or integrate custom LLM, ASR, and TTS combinations depending on control and quality requirements.
Build across web, mobile, backend, and device surfaces with a shared mental model for sessions, events, and agent behavior.
Add custom data, event subscriptions, server-side orchestration, and downstream analytics without redesigning the whole interaction loop.
Reuse Agora's voice path and session model so AI can participate as part of a live system, not only as a detached API call.
## What to read next [#what-to-read-next]
* [Conversational AI](/en/ai)
* [AI quickstart](/en/ai/get-started/quickstart)
* [OpenAI Realtime](/en/ai/reference/openai-realtime-integration)
* [API Reference](/en/api-reference/conversational-ai)
# Core concepts (/en/introduction/core-concepts)
RTC (Real-Time Communication) refers to real-time communication technology, that allows almost instant exchange of audio, video, and other data between the sender and the receiver.
Agora SDKs provide real-time audio and video interaction services, with multi-platform and multi-device support. This includes high-definition video calls, voice-only calls, interactive live streaming, as well as one-on-one and multi-group chats.
This article introduces the key processes and concepts you need to know to use Agora SDKs.
## Using the Agora Console [#using-the-agora-console]
To use Agora SDKs, create an audio and video project in the Agora Console first. See [Agora account management](/en/introduction/account) for details.

#### Agora Console [#agora-console]
[Agora Console](https://console.agora.io/v2) is the main dashboard where you manage your Agora projects and services. Agora Console provides an intuitive interface for developers to query and manage their Agora account. After registering an [Agora Account](https://console.agora.io/v2), you use the Agora Console to perform the following tasks:
* Manage the account
* Create and configure Agora projects and services
* Get an App ID
* Manage members and roles
* Check call quality and usage
* Check bills and make payments
* Access product resources
Agora also provides RESTful APIs that you use to implement features such as creating a project and fetching usage numbers programmatically.
## General concepts [#general-concepts]
Agora uses the following basic concepts:
### App ID [#app-id]
The App ID is a random string generated within [Agora Console](https://console.agora.io/v2) when you create a new project. You can create multiple projects in your account; each project has a different App ID. This App ID enables your app users to communicate securely with each other. When you initialize Agora Engine in your app, you pass the App ID as an argument. The App ID is also used to create the authentication tokens that ensure secure communication in a channel. You [retrieve your App ID](https://console.agora.io/v2/project-management) using Agora Console.
Agora uses this App ID to identify each app, provide billing and other statistical data services.
For applications requiring high security in a production environment, you must choose a **App ID + Token** mechanism for user authentication when creating a new project. Without an authentication token, your environment is open to anyone with access to your App ID.
### App certificate [#app-certificate]
An App certificate is a string generated by Agora Console to enable token authentication. It is required for generating a Video SDK or Signaling authentication token.
To use your App certificate for setting up a token server, see [Deploy a token server](/en/realtime-media/video/build/authenticate-users/deploy-token-server).
### Token [#token]
A token is a dynamic key that is used by the Agora authentication server to check user permissions. You use Agora Console to generate a temporary token for testing purposes during the development process. In a production environment, you implement a token server in your security infrastructure to control access to your channels.
After obtaining the App ID, App Certificate, and Token in the Agora Console, you can start implementing basic audio and video communication in your app.
### Channel [#channel]
Agora uses the *channel name* to identify a channel. Users who specify the same *channel name* join a common channel and interact with each other. A channel is created when the first user joins. It ceases to exist when the last user leaves.
You create a channel by calling the methods for transmitting real-time data. Agora uses different channels to transmit different types of data. The Video SDK channel transmits audio or video data, while the Signaling channel transmits messaging or signaling data. The Video SDK and Signaling channels are independent of each other.
Additional components provided by Agora, such as On-Premise Recording and Cloud Recording, join the Video SDK channel and provide real-time recording, transmission acceleration, media playback, and content moderation.
### Channel profile [#channel-profile]
The SDK applies different optimization methods according to the selected channel profile. Agora supports the following channel profiles:
| Channel profile | Description |
| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `COMMUNICATION` | This profile is suitable for one-on-one or group calls, where all users in the channel talk freely. |
| `LIVE_BROADCASTING` | In a live streaming channel, users have two client roles: *host* and *audience*. The *host* sends and receives audio or video, while the *audience* only receives audio or video with the sending function disabled. |
### Stream [#stream]
A stream is a sequence of digitally-encoded coherent signals that contains audio or video data. Users in a channel [publish](#publish) local streams and [subscribe](#subscribe) to remote streams from other users.
### Publish [#publish]
Publishing is the act of sending a user's audio or video data to the channel. Usually, the published stream is created by the audio data sampled from a microphone or the video data captured by a camera. You can also publish media streams from other sources, such as an online music file or the user's screen.
After successfully publishing a stream, the SDK continues sending media data to other users in the channel. By publishing the local stream and subscribing to remote streams, users communicate with each other in real-time.
### Subscribe [#subscribe]
Subscribing is the act of receiving media streams published by remote users to the channel. A user receives audio and video data from other users by subscribing to one or more of their streams. You either directly play the subscribed streams or process incoming data for other purposes such as recording or capturing screenshots.
### User ID [#user-id]
A User ID (UID) identifies a user in a channel. Each user in a channel should have a unique user ID. If you do not specify a user ID when the user joins a channel, a UID is automatically generated and assigned to the user.
### User role [#user-role]
A user role is used to define whether users in the channel have permission to publish streams. There are two user roles:
* Host: A user who can publish streams in a channel.
* Audience: A user who cannot publish streams in a channel. Users with this role can only subscribe to remote audio and video streams.
### Connection (RtcConnection) [#connection-rtcconnection]
The connection between the SDK and the channel. When you need to publish or receive multiple streams in multiple channels, a connection is used to specify the target channel.
### Agora SDRTN® [#agora-sdrtn]
Agora's core engagement services are powered by its Software-Defined Real-Time Network (SDRTN®) that is accessible and available anytime, anywhere around the world. The software-defined network isn't confined by device, phone numbers, or a telecommunication provider's coverage area like traditional networks. Agora SDRTN® has data centers globally that cover over 200+ countries and regions. The network delivers sub-second latency and high availability of real-time video and audio anywhere on the globe. With Agora SDRTN®, Agora can deliver live user engagement experiences in the form of real-time communication (RTC) with the following advantages:
* Unmatched quality of service
* High availability and accessibility
* True scalability
* Low Cost
# Firewall requirements (/en/introduction/firewall)
To allow you to use Agora products in environments with restricted network access, Agora provides the following solutions: the firewall whitelist and the Agora cloud proxy.
The following table lists the support of Agora products for the two solutions:
| Agora Products | Firewall Whitelist | Agora Cloud Proxy |
| ------------------------------------------ | :----------------: | :---------------: |
| Video SDK (Native, third-party frameworks) | ✘ | ✔ |
| Video SDK (Web) | ✔ | ✔ |
| Signaling SDK (Native) | ✔ | ✘ |
| Signaling SDK (Web) | ✔ | ✔ |
| On-Premise Recording SDK | ✘ | ✔ |
| Interactive Gaming SDK | ✘ | ✘ |
* When using the firewall whitelist, add the domains and ports to the firewall whitelist, and do not set restrictions on IP addresses.
* When using Agora cloud proxy, refer to [Connect to Agora through a restricted network](/en/realtime-media/video/build/manage-connection-and-quality/cloud-proxy)
### Video SDK (Web) [#video-sdk-web]
Add the following destination domains and the corresponding ports to your firewall whitelist.
#### Domains [#domains]
```text
.agora.io
.edge.agora.io
.sd-rtn.com
.edge.sd-rtn.com
.ap.sd-rtn.com
.statscollector.sd-rtn.com
.webrtc-cloud-proxy.sd-rtn.com
.rtnsvc.com
.edge.rtnsvc.com
.rtesvc.com
.edge.rtesvc.com
```
To improve connectivity over public networks, we recommend adding the domains listed above to your firewall allowlist.
#### Ports [#ports]
| Destination ports | Port type | Operation |
| -------------------------------------------------------------------------------------------------------- | --------- | --------- |
| 80; 443; 3433; 3478; 4700 - 5000; 5668; 5669; 6080; 6443; 8667; 9667; 30011 - 30013 (for RTMP converter) | TCP | Allow |
| 3478; 4700 - 5000 (2.9.0 or later); 10000 - 65535 (before 2.9.0) | UDP | Allow |
### Signaling SDK (Web) [#signaling-sdk-web]
#### Message channel [#message-channel]
For a message channel, you need to add the following content to the firewall whitelist:
* **Domains**:
```text
.edge.agora.io
.edge.sd-rtn.com
web-1.ap.sd-rtn.com
web-2.ap.sd-rtn.com
web-3.ap.sd-rtn.com
web-4.ap.sd-rtn.com
ap-web-1.agora.io
ap-web-2.agora.io
ap-web-3.agora.io
ap-web-4.agora.io
webcollector-rtm.agora.io
logservice-rtm.agora.io
rtm.statscollector.sd-rtn.com
rtm.logservice.sd-rtn.com
```
* **Ports**:
| **Destination port** | **Protocol** | **Operate** |
| ---------------------- | ------------ | ----------- |
| 443; 9591; 9593; 27387 | TCP | Allow |
Info
If you are using Signaling 1.x, also add port 9601.
#### Stream channel [#stream-channel]
For a stream channel, you need to add the following to your firewall whitelist:
* **Domains**:
```text
.agora.io
.edge.agora.io
.sd-rtn.com
.edge.sd-rtn.com
```
* **Ports**:
| **Destination port** | **Protocol** | **Operate** |
| --------------------------------------------------------- | ------------ | ----------- |
| 80; 3433; 4700 - 5000; 5668; 5669; 6080; 6443; 8667; 9667 | TCP | Allow |
| 3478; 4700 - 5000 | UDP | Allow |
### Signaling SDK (Native) [#signaling-sdk-native]
#### Message channel [#message-channel-1]
For a message channel, you need to add the following content to the firewall whitelist:
* **Domains**:
```text
.agora.io
```
* **Ports**:
| **Destination port** | **Protocol** | **Operate** |
| ----------------------------------------------------- | ------------ | ----------- |
| 443; 7384; 8443; 9130; 9131; 9136; 9137; 9140; 9141 | TCP | Allow |
| 1080; 3000; 8000; 8130; 8443; 9120; 9121; 9700; 25000 | UDP | Allow |
#### Stream channel [#stream-channel-1]
For a stream channel, you need to add the following to your firewall whitelist:
* **Ports**:
| **Destination port** | **Protocol** | **Operate** |
| -------------------- | ------------ | ----------- |
| 4001 - 4150 | UDP | Allow |
Info
The target ports listed in this section may be adjusted according to actual conditions. If you encounter any issues, contact [rtm@agora.io](mailto\:rtm@agora.io).
# Glossary (/en/introduction/glossary)
## A [#a]
### Agora Analytics [#agora-analytics]
Agora Analytics is a site for developers to track and analyze the usage and quality of calls.
### Agora Cloud Backup [#agora-cloud-backup]
Agora Cloud Backup is a backup cloud storage service used in cloud recording. If the recording service cannot upload the recorded files to the specified third-party cloud storage, then the service automatically and temporarily stores them in the backup cloud.
### Agora Console [#agora-console]
Agora Console is a site for developers to manage Agora projects and services.
### App ID [#app-id]
An app ID is a randomly generated string provided by Agora and is the unique identifier of an app.
### App certificate [#app-certificate]
An app certificate is a randomly generated string provided by Agora for enabling token authentication. It is one of the required arguments for generating a token.
### Audience [#audience]
Audience are users who do not have streaming permissions in a channel. An audience user can subscribe to remote audio and video streams, but cannot publish audio and video streams. For more information, see [user role](#user-role).
### Audience (becoming) [#audience-becoming]
Becoming an audience describes a use-case within an Interactive Live Streaming channel (the channel profile is Live-Broadcast) when a host switches the user role and becomes an audience.
### Audio mixing [#audio-mixing]
Audio mixing means combining multiple audio streams into one.
### Audio profile [#audio-profile]
An audio profile includes the sample rate, encoding scheme, number of channels, and bitrate for encoded audio data.
### Audio route [#audio-route]
The audio route is the pathway audio data takes through audio hardware components during playback.
## C [#c]
### Callee [#callee]
A callee is a Signaling user who receives a [call invitation](#call-invitation).
### Caller [#caller]
A caller is a Signaling user who sends a [call invitation](#call-invitation).
### Call invitation [#call-invitation]
Call invitation is a communication protocol based on the peer-to-peer messaging functionality of the Agora Signaling SDK. Call invitation supports starting, ending, accepting, and refusing calls.
### Channel [#channel]
In Agora's platform, a channel is a way of grouping users together and is identified by a unique channel name. Users who connect to the same channel can communicate with each other. A channel is created when the first user joins and ceases to exist when the last user leaves.
### Channel attribute [#channel-attribute]
Channel attributes are tags added to Signaling channels, including the property name, property value, the ID of the last Signaling user who updated the attribute, and the time of the last update.
### Channel message [#channel-message]
A channel message is a message that a Signaling user sends to all Signaling users in a channel.
### Channel profile [#channel-profile]
The channel profile is a configuration that Agora uses to apply optimized algorithms for different real-time use-cases.
### Cloud proxy [#cloud-proxy]
Cloud proxy is a proxy service that enables users to connect to Agora services through a firewall by using fixed IP addresses.
### CREC [#crec]
CREC is a component provided by Agora for recording and saving voice and video calls and interactive streaming on a third-party cloud storage through RESTful APIs.
### Co-hosting [#co-hosting]
Co-hosting describes a use-case with more than one host.
### Composite recording mode [#composite-recording-mode]
Composite recording mode generates a single mixed audio and video file for all UIDs in a channel.
### Custom rendering [#custom-rendering]
Custom rendering is the process where developers collect raw data from the SDK and process it according to specific needs.
### Custom source [#custom-source]
Custom source is the process where an app captures raw data by itself.
## D [#d]
### Delay [#delay]
In real-time audio and video communication, delay refers to the time elapsed from when the data is sent to when it is received.
### Dual-stream mode [#dual-stream-mode]
In the dual-stream mode, the Video SDK simultaneously transmits a higher-resolution video stream along with an additional low-resolution, low bitrate video stream.
## F [#f]
### Freeze [#freeze]
Freeze refers to choppy audio or video playback caused by a poor network connection or limited device performance during real-time audio and video communication.
## H [#h]
### High-quality video stream [#high-quality-video-stream]
In dual-stream mode, the SDK transmits two video streams of differing quality at the same time. See [dual stream mode](#dual-stream-mode) for details.
### Host [#host]
The host refers to a user who has streaming permissions in a channel. A host can publish audio and video. A host may also subscribe to audio and video published by other hosts.
### Host (becoming) [#host-becoming]
Becoming a host describes a use-case within an Interactive Live Streaming channel (the channel profile is Live-Broadcast) when an audience switches the user role and becomes a host.
## I [#i]
### Individual recording mode [#individual-recording-mode]
Individual recording mode records audio and video of each UID as separate files.
### Inject online media stream [#inject-online-media-stream]
Inject online media stream refers to injecting an online media stream in an Interactive Live Streaming channel to share the stream with all users in the channel. The Agora Video SDK provides a method for developers to inject an online mixed audio and video stream or an audio only stream to a channel.
### Interactive Live Streaming [#interactive-live-streaming]
Enabled by either Agora’s Video SDK or Voice SDK, Interactive Live Streaming gives you full control over the streaming experience from a standard one-to-many stream to a highly-interactive live event.
## J [#j]
### Jitter [#jitter]
In real-time audio and video communication, jitter is the variation in the delay of data packets transmitted continuously on the network.
## L [#l]
### Last mile [#last-mile]
The last mile refers to the network between the Agora edge server and the end user's device.
### Loopback test [#loopback-test]
A loopback test sends a signal from a communication device and is then returned (looped back) to it. It is often used to determine whether a device is working properly.
### Low-quality video stream [#low-quality-video-stream]
In dual-stream mode, the SDK transmits two video streams of differing quality at the same time. The low-quality video stream has a lower resolution and bitrate than the high-quality video stream. See [dual stream mode](#dual-stream-mode) for details.
## M [#m]
### MediaPlayer kit [#mediaplayer-kit]
The mediaplayer kit is a plug-in of the Video SDK to play local and online media resources and publish the media streams to other users in an Interactive Live Streaming channel.
### Media stream [#media-stream]
A media stream is an object that contains media data.
### MPUSH [#mpush]
MPUSH enables you to upload audio and video streams from Agora channels and upload them to a Content Delivery Network (CDN) to reach a larger audience.
### Mirror [#mirror]
Mirroring is an effect that a video image renders.
## O [#o]
### Offline [#offline]
Offline describes the status of a Signaling user who has successfully logged out of Signaling.
### Offline message [#offline-message]
An offline message is a peer-to-peer message that an online Signaling user sends to an offline Signaling user.
### Online [#online]
Online describes the status of a user who has successfully logged in to the Agora Signaling system or stays disconnected from the Agora Signaling system for more than 30 seconds.
### OPREC [#oprec]
OPREC is a component provided by Agora for recording and saving voice and video calls and interactive streaming on a Linux server.
## P [#p]
### Packet loss [#packet-loss]
Packet loss refers to the data packets transmitted on the network failing to arrive at their intended destination.
### Peer-to-peer message [#peer-to-peer-message]
A peer-to-peer message is a message that an online Signaling user sends to an online or offline user.
### Publish [#publish]
Publishing is the action of sending the user's audio and/or video data to the channel.
## R [#r]
### Raw data [#raw-data]
Raw data, including raw audio data and raw video data, is the unprocessed data which developers can collect during real-time communication.
### Render the first video frame [#render-the-first-video-frame]
Rendering the first video frame is the action of rendering the first video frame on the local device.
## S [#s]
### Agora SDRTN® [#agora-sdrtn]
Software-Defined Real-Time Network (SDRTN®) is a real-time transmission network built by Agora and is the only network infrastructure specifically designed for real-time communications in the world.
### Signaling SDK [#signaling-sdk]
You use the Signaling SDK to implement real-time messaging use-cases that require low latency and high concurrency for a global audience.
### Slice [#slice]
Slicing means cutting recorded audio or video into separate files according to specific rules. During an Agora CREC, the recording service cuts the streams and generates multiple slice files (TS or WebM files) and M3U8 files that serve as a playlist of the slice files.
### Sound localization [#sound-localization]
Sound localization means determining the distance to and direction of a sound through hearing the difference of volume, time, and timbre between users' ears.
### Stream fallback [#stream-fallback]
In use-cases where multiple users engage in real-time audio and video communication, user experience can be impaired if the network condition is too poor to guarantee both audio and video at the same time.
### Stream mixing [#stream-mixing]
Stream mixing means combining multiple media streams into one. It may include the mixing of video streams (video mixing) and audio streams (audio mixing).
### Subscribe [#subscribe]
In the Agora Video SDK, subscribing is the action of receiving media streams published to the channel. In the Agora Signaling SDK, subscribing is the action of monitoring the online status of one or multiple Signaling users.
## T [#t]
### TCP [#tcp]
TCP (Transmission Control Protocol) is a connection-oriented and reliable transport layer communication protocol.
### Token [#token]
A token, also known as a dynamic key, is used for authentication when an app user joins a channel or logs onto the Agora Signaling.
### Transcoding [#transcoding]
Transcoding is the process of decoding audio and video data and then re-encoding them into the target conversion output or format.
## U [#u]
### UDP [#udp]
UDP (User Datagram Protocol) is a connectionless-oriented and unreliable transport layer communication protocol.
### User attribute [#user-attribute]
User attributes are tags added to Signaling users, including property names and property values.
### User ID (uid) [#user-id-uid]
In the Agora Video SDK, a user ID identifies a user in the channel. The user ID is a 32-bit signed integer, with a value range from -231 to 231-1, that you can specify yourself. If you specify `0` for the user ID when joining a channel, the SDK generates a random number and returns the value in the join channel success callback.
In the Agora Signaling SDK, a user ID identifies a user in Signaling.
The user ID in the Agora Video SDK and the Agora Signaling SDK are independent of each other.
### User role [#user-role]
The type of user role determines whether the user in the channel has streaming permissions.
## V [#v]
### Video layout [#video-layout]
Video layout arranges the display of users when multiple users are mixed into one stream, such as in Media Push or a composite recording.
### Video mixing [#video-mixing]
Video mixing means combining multiple video streams into one.
### Video profile [#video-profile]
The video profile refers to a set of video attributes, such as resolution, bitrate, and frame rate.
### Video SDK [#video-sdk]
An SDK developed by Agora to enable developers to add real-time audio interaction to their projects.
### Voice SDK [#voice-sdk]
Agora provides the Voice SDK to enable real-time audio communication.
## W [#w]
### Web page recording mode [#web-page-recording-mode]
In web page recording mode, the content and audio of a specified web page are recorded in a single file.
# Agora Documentation (/en/introduction)
## Build an Agent [#build-an-agent]
## Build Real-Time Applications [#build-real-time-applications]
## Start with AI Tools [#start-with-ai-tools]
## Realtime use cases [#realtime-use-cases]
## SDKs and APIs [#sdks-and-apis]
## Get Help [#get-help]
# Members & Roles (/en/introduction/members-roles)
## Manage members and teams [#manage-members-and-teams]
Once you add members to your Agora account, you are assigned to the **Admin** team and can add these members to different teams with specified permissions.
### Add a member [#add-a-member]
1. Log in to Agora Console, click your account name in the top-right corner, and click **Settings**.
2. In the left navigation panel, click **Teams and members**, then select the **Members** tab.
3. Click **Add New Member**, fill in the email address of the new member, and choose a team from the dropdown.
4. Click **OK**.
5. Agora sends a confirmation email to this address. The new member must follow the instructions in the email to finish joining the project.
### Manage members [#manage-members]
On the **Teams and members** page, **Members** tab, you can do the following:
* Change the team that a member belongs to.
* Delete a member.
Only the main account can delete a member account. Member accounts assigned to the **Admin** team cannot delete a member account.
### Manage teams and permissions [#manage-teams-and-permissions]
On the **Teams and members** page, **Teams** tab, you can view the permissions assigned to teams. Agora predefines the following teams:
* **Admin**: full access to all projects; can view usage data and finance information, manage members and teams, manage projects, and view Analytics reports.
* **Finance**: can view finance information for all projects.
* **Product/Operation**: can view usage data for all projects.
* **CS/Maintenance**: can view Analytics reports for all projects.
* **Engineer**: can manage projects and view Analytics reports for all projects.
### Add a custom team [#add-a-custom-team]
1. On the **Teams and Members** page, **Teams** tab, click **Add New Team**.
2. Fill in the team name.
3. Select the permissions of this team in the **Usage**, **Finance**, **Teams and Members**, **Project**, **Analytics**, and **Data** columns.
4. Click **OK**.
# Messaging & Presence (/en/introduction/messaging-presence)

Messaging & Presence is the capability layer for products that need more than media. This category covers human messaging, participant presence, room state, and shared collaboration surfaces that keep live systems coordinated.
Agora's product map for this layer includes:
* **IM / Chat**
* **RTM / Signaling**
* **Interactive Whiteboard**
## What this category is good for [#what-this-category-is-good-for]
This category is the right entry point when you need:
* in-room chat and social messaging
* user presence and online state
* metadata synchronization and coordination signals
* collaborative session control and shared state
* whiteboard-backed visual collaboration inside a live product
The shared requirement is that participants must stay aligned with each other and with the state of the session in real time.
## Capability map [#capability-map]
IM-oriented communication for user conversation, room interaction, and product workflows that depend on message exchange.
RTM-oriented channels, topics, presence, metadata, and coordination events that support realtime systems.
Shared visual state for drawing, annotation, presentation, and collaborative interaction around content.
## Start here [#start-here]
## Key characteristics [#key-characteristics]
Support both user-facing communication and the metadata flows that keep live products synchronized.
Track who is online, what state they are in, and how session behavior should react as users and devices change status.
Extend from text and events into shared whiteboard or content collaboration when the product needs a richer interaction layer.
Combine easily with RTC sessions and AI agents when messages and events need to travel alongside audio and video.
Choose between IM-style user communication and RTM-style signaling depending on whether the main problem is conversation, coordination, or both.
Keep users, agents, and session tools operating against the same live room state instead of fragmenting the product experience.
## What to read next [#what-to-read-next]
* [Realtime Messaging RTM](/en/realtime-media/rtm)
* [Instant Messaging IM](/en/realtime-media/im)
* [Interactive Whiteboard](/en/realtime-media/whiteboard)
# Projects (/en/introduction/projects)
## Why this page matters [#why-this-page-matters]
In Agora, a project is the operational boundary for a product environment. It holds the App ID, certificate configuration, enabled services, and billing context that the rest of your integration depends on.
If your project setup is wrong, the rest of the docs may still be correct but your implementation will fail later through missing credentials, disabled services, or the wrong security mode.
## What you do here [#what-you-do-here]
Use the **Projects** page in Agora Console to:
* create a new project for a product or environment
* choose the authentication mode for that project
* copy the App ID used by clients and services
* manage App Certificates for token-based access
* check whether the project is configured the way your integration expects
## Recommended setup [#recommended-setup]
For most teams:
* create separate projects for development, testing, and production
* use **App ID + Token** instead of App ID only
* keep certificate management on the server side
* confirm the correct project is being used before debugging client or backend code
## Create a project [#create-a-project]
1. Open the **Projects** page in Agora Console.
2. Click **Create New**.
3. Enter a project name and use case.
4. Select **App ID + Token (Recommended)** as the authentication mechanism.
5. Click **Submit**.
## What to check in project details [#what-to-check-in-project-details]
On the project details page, confirm that you can:
* copy the App ID used by your application
* view whether token-based security is enabled
* access the App Certificate when your backend needs token generation
* verify that the project is the one your current environment should use
## Service access [#service-access]
To access most Agora products you need:
* An App ID.
* An App Certificate if your backend generates tokens.
* A project with the required service enabled.
For production, use token-based authentication instead of App ID only.
## What to verify before launch [#what-to-verify-before-launch]
Before moving beyond a prototype, confirm:
* the required service is enabled for the correct project
* the authentication mode matches your intended production design
* the billing unit makes sense for your usage pattern
* the right people on your team can access billing and financial records
## Manage App Certificates [#manage-app-certificates]
Agora provides two certificate roles:
* **Primary Certificate** for normal token generation
* **Secondary Certificate** for production rotation and controlled cutovers
If you suspect a certificate is compromised:
1. Enable the Secondary Certificate.
2. Swap the certificates.
3. Disable the old secondary certificate.
4. Delete the original primary certificate after clients and services have moved over.
Deleting a certificate invalidates all tokens generated with that certificate.
## Restrictions [#restrictions]
* If your account has multiple members, only those assigned to the **Admin**, **Engineer**, or an authorized custom team can access the **Projects** page.
* Each Agora account can create up to 20 projects. If you need to create more projects, submit a support ticket.
## Where to go next [#where-to-go-next]
* Open [Console setup](/en/introduction/console-setup) to understand the first-time console workflow around credentials and service activation.
* Read [Billing](/en/introduction/billing) to understand free allowances, settlement, and package options before launch.
* Read [Security and privacy](/en/introduction/security-privacy) before production rollout.
# Real-Time Voice & Video (/en/introduction/realtime-audio-video)

Real-Time Audio & Video is the capability layer for products where live media is the primary user experience. This category covers two-way voice and video interaction, host-audience live experiences, large-scale broadcast delivery, and device-oriented realtime connectivity.
Agora's product map for this layer includes:
* **Voice Calling**
* **Video Calling**
* **Interactive Live Streaming**
* **Fusion CDN broadcast delivery**
* **IoT / RTSA edge-device connectivity**
## What this category is good for [#what-this-category-is-good-for]
This category is the right entry point when you need:
* one-to-one or group voice and video calling
* meetings, classrooms, and telehealth sessions
* creator-led or host-audience live interaction
* broadcast-style delivery to larger audiences
* smart-device, camera, or edge-terminal media connectivity
The shared requirement is stable, low-latency media transport with clear session behavior across networks, devices, and participant roles.
## Capability map [#capability-map]
Low-latency voice interaction for calling, social audio, customer communication, and lightweight live sessions.
Realtime face-to-face interaction for meetings, classrooms, telehealth, and collaboration products.
Host-audience interaction for creator events, live communities, and interactive broadcast-style products.
Fusion CDN-based distribution for larger viewer populations and playback-oriented live delivery.
RTSA-based device connectivity for smart cameras, displays, embedded endpoints, and IoT-oriented realtime media.
## Start here [#start-here]
## Key characteristics [#key-characteristics]
Use the same realtime transport model across calling, live interaction, broadcast expansion, and device connectivity.
Support peer-to-peer, group, host-audience, and device-oriented topologies without switching to a different conceptual model.
Start from interactive sessions, then extend into larger audience delivery or downstream cloud media services.
Reach web, mobile, desktop, and edge-device products with consistent session and media semantics.
Combine media transport with messaging, AI, transcription, recording, and analytics as product requirements grow.
Keep the live experience usable under real-world packet loss, geography, and device constraints.
## What to read next [#what-to-read-next]
* [RTC](/en/realtime-media/rtc)
* [Fusion CDN](/en/realtime-media/fusion-cdn)
* [RTSA](/en/realtime-media/rtsa)
# Security & Privacy (/en/introduction/security-privacy)
## Information security policy [#information-security-policy]
This section provides information security policy for Agora services.
Agora services provide built-in encryption and customized encryption. You can use either of them to implement encryption. The following diagram describes the encrypted data transmission process:

#### Purpose [#purpose]
Agora is committed to safeguarding the confidentiality, integrity, and availability of all users' physical and electronic information assets.
* Confidentiality against unauthorized access and eavesdropping
* Integrity against tampering and forgery
* Availability of data transmission through the Agora SDRTN®
#### Scope [#scope]
This article describes how Agora protects customer data with security controls.
### Data classification [#data-classification]
All customer data, in all formats or media types, is classified according to the following categories and protected accordingly.
| Category | Description |
| --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Customer Account Data | Information related to the customers' Agora accounts, including customer ID, customer IP, network type, operating system, email address, telephone, product interest, programming platform, UTM information, identity, company name, company URL, billing information, trade information, purchased package information, project status, ticket, member information and console access records. |
| End User Data | Information collected by customers related to end users' personal devices and network, including microphone and camera information, CPU status, memory status, battery status, system version, phone model, phone signal level, received signal strength indicator (RSSI), network type, user attributes and channel attributes. |
| Call Content | Audio and video data of the user during a call. |
| Log | Media server logs generated by the Agora servers when accessing the Agora SDRTN®. Media server logs do not contain text messages or personal information. |
### Data security [#data-security]
The communication between the user and the Agora server is protected by transmission protocols, such as the Agora private transmission protocol, Transport Layer Security (TLS) and WebSocket Secure (WSS). You can also use Advanced Encryption Standard (AES) or a customized encryption algorithm for the encryption of audio and video data.
During data transmission, the Agora SDRTN® does not transmit any encryption key information. Call content information can only be decrypted on the terminal device (such as the client app and the customer's on-premise recording server) through the client authorization key.
### Data availability [#data-availability]
* Large and distributed data centers: Agora has multiple data centers providing services globally, and any attack on one data center cannot affect others.
* Rapid recovery: When a data center is subjected to malicious attacks that are difficult to prevent, such as a distributed denial-of-service (DDoS) attack, Agora will automatically isolate the data center and avoid affecting users' services.
* DDoS attack prevention: Agora has deployed anti-DDoS firewalls in each core cloud data center. Agora has more than two hundred distributed data centers around the world, which guarantees sufficient capabilities and resources to control the risk of DDoS attacks.
### Data storage [#data-storage]
Agora provides customers with the Agora On-Premise Recording SDK and Agora Cloud Recording, enabling customers to record part or all of the call contents. When using the recording services, all recorded video or audio files are stored on the storage server provided by the customer.
### Access authorization [#access-authorization]
End users can access the Agora SDRTN® using a dynamic key. For details, see [Secure authentication with tokens](/en/realtime-media/video/build/authenticate-users/authentication-workflow).
### Electronic access controls [#electronic-access-controls]
Agora strictly controls the data access on all internal systems. All users have independent internal accounts and authorization systems, and must pass two-step verification. All access records are recorded.
All servers involving user data are strictly audited and protected. Agora employees will only access the production server when necessary, and only by obtaining temporary authorization. Operation records are kept for the whole process.
### Physical access controls [#physical-access-controls]
All operating servers are hosted in computer rooms that meet ISO 27001 or above information security management certification standards, and all computer rooms protect information security in accordance with the escrow agreement or the Data Protection Association (DPA). Both third-party hosting providers and Agora employees must be approved before they can access servers and equipment.
### Roles and responsibilities [#roles-and-responsibilities]
Agora sets the responsibilities for its own staff and customers. See the following sections for more information.
### Roles and responsibilities within Agora [#roles-and-responsibilities-within-agora]
Security roles and responsibilities within Agora are categorized as follows:
| Role | Responsibilities |
| ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Information Security Sub-Committee | The Information Security Sub-committee (ISSC) is responsible for the development and implementation of policies and procedures. The ISSC monitors company adherence, and conducts regular technical and non-technical evaluations of Agora security policies. The ISSC also designates which employees are authorized employees. |
| Authorized Employee | An authorized employee has access to the production machines required for the support and maintenance of the Agora service as determined by the ISSC. |
| All Other Staff | All other staff are required to maintain confidentiality as required by their terms of employment and are required to immediately report any security breach. |
### Shared responsibilities [#shared-responsibilities]
When evaluating the Agora SDRTN®, it is important for customers to understand and distinguish the following security measures:
* The security measures that Agora implements and operates.
* The security measures that customers implement and operate to ensure the security of call content information and applications when using the Agora SDRTN® services.
Customers retain control of the security measures they choose to protect their personal information, platform, applications, systems, and networks. Agora customers should be responsible for all information they collect from users and themselves, such as application logins, identities, passwords, payment information, names, and addresses.
Agora recommends adding obvious prompts before accessing the users' personal information. For example, add an Enable/Disable button with which the user can agree or disagree to share personal information.
### Security training [#security-training]
Agora conducts information protection, security, and compliance training for all new employees. Agora also ensures that all employees receive information confidentiality training at least once a year. All authorized employees (employees who have access to the production machines) will receive additional training.
### Non-compliance [#non-compliance]
Employees must comply with confidentiality agreements and internal security systems. If a non-compliant situation occurs, Agora will take the corresponding measures depending on the severity of the situation, including but not limited to having conversations with the at-fault employees, strengthening training and education, dissolving labor agreements, and pursuing other legal liabilities.
### Reporting potential security risks [#reporting-potential-security-risks]
Please report any potential risks of Agora services that you may notice to [security@agora.io](mailto\:security@agora.io). Please use our PGP public key (Key ID: 2F4553BE) to encrypt your report, as potential security risks are usually sensitive. We assure you that our security experts will handle the issue immediately once they receive your report.
To facilitate troubleshooting and verification, please include the following information in your email:
* Your contact information.
* The version of the impacted SDK or solution.
* Description of the potential risks.
* Miscellaneous technical details, such as your system configurations, methods or steps for reproducing the issue.
To better protect our system and customers, Agora also invites security researchers to report any bugs or vulnerabilities they discover to us. Click [Bug Bounty](#agora-bug-bounty-program) to learn more.
### Security FAQ [#security-faq]
To ensure the security of transmitted data, Agora services provide encryption for audio and video data. Customers can use AES-128/AES-256 or other algorithms preset by Agora, or use customized encryption algorithms. For details, see [Channel Encryption](/en/realtime-media/video/build/secure-and-protect-channels/media-stream-encryption). The encryption key is completely generated and distributed by the customer. Agora recommends that customers use separate keys for each channel to achieve the highest level of data security.
For network protocol encryption, Agora SDKs use AES-128 mode to encrypt network payloads.
For channel encryption, Agora SDKs support AES-128/AES-256 mode to encrypt audio and video. For those who have higher requirements for content security, Agora recommends using our customized encryption. Note that the On-Premise Recording SDK and Cloud Recording do not support customized encryption. For details, please send an email to [support@agora.io](mailto\:support@agora.io).
Yes.
Agora regularly scans core network nodes to check and clear possible security holes. Agora also configures anti-DDoS firewalls in each core cloud data center to protect them from attacks. Agora has more than two hundred distributed data centers around the world, which provides sufficient capabilities and resources to control the risk of DDoS attacks.
When using Agora services, the audio and video data are transmitted through the Agora servers. Agora servers cache the audio and video data for 10 seconds during the transmission and release all audio and video data immediately after the call.
## Whitepaper [#whitepaper]
People engage longer when they see, hear, and interact with each other. The future of meaningful human connections is made possible now with Agora’s Real-Time Engagement Platform. People rely on Agora’s Real-Time Engagement Platform to exchange millions of calls and messages, with vivid voice and video embedded in any application, on any device, anywhere.
Agora, Inc. is proud to offer a professional Real-Time Engagement Platform as a Service (RTE PaaS) with Compliance, Safety, Security, and Trust. Our commitment to compliance, data and information security, and privacy protection is part of the core values of our company. From our Software-Defined Real-Time Network (SDRTN®) architecture to our day-to-day business operations, Agora continually invests in innovations and business processes that build trust with our customers, investors, and developer community. Agora works to high standards to follow the best security practices and comply with strict privacy regulations and standards as we respect the privacy of all our customers.
The information contained in this document is intended to provide transparency in relation to Agora’s security stance and processes. If you think you may have found a security vulnerability within any of Agora’s services, please contact our security team directly at [security@agora.io](mailto\:security@agora.io).
Agora strives to incorporate security into all our products and services and integrates the best security practices into everyday business operations. To meet these primary goals and improve the overall information security posture in an efficient and effective manner, Agora has built its security framework against the ISO/IEC 27001 Information Security Management Standard.
As threats to information security continue to evolve, having dedicated security resources is essential. Agora’s Executive Security Committee meets regularly to address security concerns and coordinate company-wide security initiatives. The Agora Strategic Security Program Roadmap has been developed and approved by the committee to guide the implementation of our security programs. Our dedicated security team, led by the Chief Information Security Officer, has the responsibility for building and enforcing information security programs.
Agora is always looking to better protect its systems and customers. Therefore, we continuously monitor and improve our information security programs by implementing the following:
* Agora conducts an internal audit of its information security management system at least once a year to ensure effectiveness;
* Agora follows industry best practice software development lifecycle management processes and conducts internal security reviews and testing before deployment into production systems;
* Agora engages third-party security experts to carry out regular penetration tests;
* Through our Bug Bounty Program, Agora works with security researchers to keep our customers' data more secure by identifying and reporting vulnerabilities in our products and services.
Agora adheres to regional and international information security standards as well as industry requirements and is committed to using international best practices. We engage with independent third parties to verify the compliance of Agora. Certified by various reputable agencies across the globe, we are recognized by industry and security organizations for excellence.
**ISO/IEC 27001 Information Security Management Standard**
Agora is certified to ISO/IEC 27001:2022 by DNV GL, demonstrating our information security maturity level. Our security team implements the Information Security Management System in partnership with Ernst & Young. Security is a top priority at Agora, and this achievement demonstrates our commitment and continuous efforts to improve the efficiency of information security controls.
Download the certificate: [ISO/IEC 27001:2022](https://docs.agora.io/en/assets/files/Agora_ISO_27001-d408e0245f77e7e09e0eef8527103483.pdf)
**ISO/IEC 27018 Information Technology – Security techniques – Code of practice for protection of personally identifiable information (PII) in public clouds acting as PII processors**
Agora is certified to ISO/IEC 27018 by DNV GL. This standard is a Code of Practice for protecting personal data in the cloud environment. Agora continuously strives to protect our customers' sensitive data.
Download the certificate: [ISO/IEC 27018:2019](https://docs.agora.io/en/assets/files/Agora_ISO_27018-0f20342310eefc1424b81ff98caf707d.pdf)
**SOC 2 Report**
Agora is confident in our security practices, and we continue to engage independent third parties to perform strict SOC 2 audits on our internal processes, security controls, and the design of Agora products. We meet the audit requirements set by the American Institute of Certified Public Accountants (AICPA) standards for security, availability, and confidentiality, and have obtained a SOC 2 report.
**PCI DSS**
Agora prioritizes the security and privacy of its customers. Since we are neither a merchant nor a service provider as defined by the PCI Security Standards Council, we do not need to conduct an annual PCI assessment. However, Agora ensures that all transactions and customer data are handled with the highest level of security, in alignment with industry best practices. Should you have any questions or require further information regarding our security measures, please feel free to contact us at [security@agora.io](mailto\:security@agora.io).
**General Data Protection Regulation - GDPR**
Agora is aligned with GDPR and we are committed to providing GDPR-compliant products and services to our customers in the EU region or with our customers who conduct business within the EU.
**Health Insurance Portability and Accountability Act - HIPAA**
Agora is aware of the sensitivity of transmitting and processing health information and we have invested in both the creation and ongoing maintenance of a HIPAA compliance program.
**California Consumer Privacy Act - CCPA**
The CCPA is the first comprehensive privacy law in the United States that aims to provide a variety of privacy rights to California consumers. As a service provider, Agora is aligned with CCPA through the implementation of our security programs.
**Children’s Online Privacy Protection Act - COPPA**
The COPPA regulates the privacy protection requirements for children under the age of thirteen. Agora has engaged privacy experts in meeting the requirements of COPPA.
Agora strongly believes in the principles of Secure by Design and Defense in Depth. Therefore, Agora adopts industry-recognized security standards and best security practices at every layer, from infrastructure to application, to perfect our products and environment and to secure the organization and our customers.
Securing access to your environment starts with identity and access controls. Agora provides you with a solution to ensure that only authorized people can access your services and resources. The Agora Console is a role-based access control tool that you use to restrict access based on the "need-to-know" principle. The console is an interactive interface where you can easily create accounts, remove members, and assign roles and permissions. This tool can help you enforce your security policies.
Furthermore, Agora provides static key, dynamic key, and hybrid authentication methods to secure the communication channels in different use cases.
At Agora, you choose how your content is secured. We offer you various options for your content in transit and provide you with full control of your own encryption keys. These features include:
* Agora Software Development Kits (SDKs) provide built-in encryption algorithms, including AES-128 and AES-256, to protect all data transmitted between the end users and Agora services.
* We also provide you with the choice to use your own encryption algorithm to protect users’ media streams during real-time engagement. The encryption key is completely under your control.
* The communication between the end users and Agora network (that is, Agora SDRTN®) is protected by encrypted transmission protocols such as the Agora Private Transmission Protocol, Transport Layer Security (TLS) and WebSocket Secure (WSS).
Agora offers our customers the ability to record real-time communication with Agora On-Premise Recording SDK and Agora Cloud Recording SDK. The recording files can be stored on users’ local devices or in a designated cloud storage service chosen by our customers. The local recordings or cloud recordings can be further encrypted through any encryption form of your choosing.
Agora does not store any streaming data or user data except for caching for transmission purposes. The cached streaming data of users will be immediately released after the service.
Data centers hosting Agora services are maintained by certified and industry-leading cloud service providers, offering state-of-the-art physical protection for the servers and infrastructure that comprise the Agora environment.
The production environment, where all our customers' data and functional servers reside, is completely separated from our internal organization network, including the development and testing environments. This guarantees that all our customers' data will stay in the production environment and never be used for development or testing purposes.
Access to the Agora production and non-production network is minimized to the greatest extent. Agora also implements network segmentation in the production network based on various factors, such as the type of business, the criticality of data, and potential risks, to secure sensitive customer data.
**DDoS prevention**
Agora regularly scans our core network nodes in the production environment to check and clear potential security vulnerabilities. Anti-DDoS firewalls are configured in each core cloud data center for protection. With more than two hundred distributed data centers around the world, Agora can provide sufficient capabilities and resources to minimize the impact of DDoS attacks and ensure high availability of real-time video and audio anywhere around the globe.
**Monitoring, logging and analysis**
Agora continuously monitors and analyzes log events to gain a comprehensive view of the security state of our production environment. The logging covers both successful and unsuccessful security events, with an emphasis on the event data of critical infrastructure.
To provide customers with better visibility and security insights, Agora Analytics is made available to consumers as a tracking and analysis tool. This tool enables customers to efficiently locate quality issues and identify root causes for a better end user experience.
The tool contains the Real-Time Alarm function, which enables you to monitor call quality and informs you in real time when the user’s communication experience is below expectation. With Real-Time Alarm, you can undertake the following actions in real time:
* Monitor users who are having a poor communication experience.
* Identify abnormalities, analyze quality factors, and locate the source of an abnormal issue.
**Network geo-fencing**
Agora has embedded Network Geo-Fencing in Real-Time Voice, Video and Messaging SDKs to ensure your data is protected against rising concerns about network security and privacy breaches. Agora Network Geo-Fencing establishes a virtual boundary within Agora SDRTN®, and you have the choice to restrict your network traffic to one or more designated regions.
**Network redundancy**
Agora has more than 200 data center POPs (Points of Presence) across the world, covering the United States, Europe, China, Japan, India, the Asia-Pacific region, and other areas. The POPs in the SDRTN® network adopt the full mesh topology with superior routing capabilities. This ensures that the network services are not interrupted due to a single point of failure. The POPs build fault tolerance and disaster recovery capabilities for Agora services across regions.
POPs also measure the performance of every possible path through the global network to find the “optimized” paths to ensure high data packet delivery success rate within the smallest time window.
Agora provides expert guidance to our customers on how to leverage our security features and embed best practices into every layer of your application.
Agora is continuously monitoring, auditing, and improving the design and operating effectiveness of our security controls. These activities are regularly performed by both third-party credentialed assessors and Agora’s internal risk and compliance team. Audit results are shared with senior management and all findings are tracked to resolution in a timely manner.
In addition to third-party security compliance audits, Agora engages Trustwave SpiderLabs to conduct network penetration tests at least annually. Results of the penetration testing are shared with senior management and are triaged, prioritized, and remediated in a timely manner. Agora customers may receive executive summaries of these activities by requesting them from their account managers.
Data security and user privacy are the top priorities of Agora. Agora is committed to building a professional RTE PaaS with Compliance, Safety, Security, and Trust. It is a critical responsibility for Agora to help ensure the confidentiality, integrity, and availability of systems and data, and Agora continues to work hard to maintain that trust. If you have any questions or concerns, please contact our security team or account managers.
## ISO certificates [#iso-certificates]
Security and compliance are basic requirements for real-time interaction. Agora complies with the compliance requirements of different countries and industries to create safe and reliable cloud services.
The ISO certification of Agora has passed the supervision and accreditation of three different certification organizations: IFA CNAS (China National Accreditation Service for Conformity Assessment), UKAS (United Kingdom Accreditation Service), and DNV (Det Norske Veritas). Agora builds cloud service technology worldwide in accordance with internationally recognized standards to ensure the information security of global customers.
#### ISO/IEC 27001:2022 [#isoiec-27001]
ISO/IEC 27001:2022 is the most authoritative, widely accepted, and applied system certification standard in the field of information security. This system covers information security management during the global operation and maintenance of Agora's cloud platform for real-time engagement.
Download the certificate: [ISO/IEC 27001:2022](https://docs.agora.io/en/assets/files/Agora_ISO_27001-d408e0245f77e7e09e0eef8527103483.pdf)
#### ISO/IEC 27017:2015 [#isoiec-27017]
ISO/IEC 27017:2015 is a supplementary standard to ISO 27001, which provides information security implementation specifications for cloud service providers.
Download the certificate: [ISO/IEC 27017:2015](https://docs.agora.io/en/assets/files/Agora_ISO_27017-b32474dd5f07aff909b250691670509c.pdf)
#### ISO/IEC 27018:2019 [#isoiec-27018]
ISO/IEC 27018:2019 is a supplementary standard to ISO 27001, which provides implementation specifications for the protection of personal information for cloud service providers.
Download the certificate: [ISO/IEC 27018:2019](https://docs.agora.io/en/assets/files/Agora_ISO_27018-0f20342310eefc1424b81ff98caf707d.pdf)
#### ISO/IEC 27701:2019 [#isoiec-27701]
ISO/IEC 27701:2019 is a privacy extension to ISO/IEC 27001 information security management and ISO/IEC 27002 security controls. It is an international management system standard that provides guidance on the protection of personal privacy, including how organizations should manage personal information.
Download the certificate: [ISO/IEC 27701:2019](https://docs.agora.io/en/assets/files/Agora_ISO_27701-7b06782b01b6c2cdcc97d610ccbd802f.pdf)
## Security best practices [#security-best-practices]
Security and compliance are essential for real-time engagements through technology. In order to provide safe and reliable cloud services, Agora adheres to the compliance requirements of different countries, regions, and industries, in addition to being certified to ISO/IEC 27001. For more information, see [ISO certificates](#iso-certificates).
Agora products and services are designed and built with multiple protection measures against attacks commonly seen in the real-time engagement industry. This article describes some of the security best practices that Agora has adopted, as well as security tools it provides for developers, as follows:
| Protection measures | Applied by default | Recommended use cases |
| -------------------------- | ------------------ | ---------------------------------------------------------------------------------------------------------- |
| Channel separation | Yes | All real-time use cases. |
| Token-based authentication | No | All real-time apps in a production environment should use Token-based end-user authentication. |
| Encryption | No | Real-time use cases that require confidentiality. |
| Network geofencing | No | Real-time use cases where customers wish to restrict access to Agora servers to within a specified region. |
### Level 1 - Channel separation [#level-1---channel-separation]
The channel architecture is the first built-in layer of protection. Agora creates an independent and isolated channel for each audio, video, or messaging data transmission. All channels are logically separated, and only authenticated users from the same App ID can join the same channel.
### Level 2 - Token-based authentication [#level-2---token-based-authentication]
The second layer of protection is the authentication layer. This is implemented through dynamic Token-based authentication. The Token is a short-lived access key that is generated by the app backend and allows users to access the Agora platform after the user has been properly validated by the app.
A Token is generated with important information such as App ID, user ID (`uid`), channel name, and expiration date.

The app developer can enable Token-based authentication (App Certificate) on [Agora Console](https://console.agora.io/v2). When enabled, all users' requests to join a channel must use a valid Token.

As a security best practice, set the Token expiration time (24 hours by default). A Token has three expiration timestamps:
* Token expiration: How long a Token is valid for, that is, how long a user can stay in a channel.
* Privilege expiration:
* Join a channel: How long a Token can be used to join a channel.
* Streaming privilege: Whether a user can send audio, video, or messaging to the channel, or join as an audience member. This privilege is not enabled by default.
### Level 3 - Encryption [#level-3---encryption]
The next layer of security in the Agora platform is encryption. Agora supports transmission encryption and data encryption.
To guarantee data confidentiality during transmission, Agora uses the AUT (Agora Universal Transport) encryption protocol, Agora's proprietary secured transport layer.
Data encryption encrypts all the audio and video streams with a symmetric key and encryption controlled by the app developer. At this level, the app provides a symmetric encryption key to the local SDK libraries.
The SDK encrypts all the captured media using the key and the configured AES-128/256 encryption. The data is sent encrypted to Agora SDRTN® and from there to the other endpoints in the channel. The receiving endpoint uses the key provided by the app layer to decrypt the media streams and send them to the renderers. With this method, only the application knows the keys. In the Native SDK (iOS, Android, macOS, Windows), the keys are not sent to Agora servers.

When using other Agora services like Web SDK, Cloud Recording, Content Moderation, and Transcoding, encryption is done a bit differently and it is not end-to-end. In this case, media is still encrypted, but the Agora service needs to be aware of the key to be able to connect to the channel and provide the service. For example, in Web SDK, user/browser protection is provided through web server protection (HTTPS) as well as WebRTC standard security practices, such as encryption and key management. More information on WebRTC security can be found [here](https://webrtc-security.github.io/).
Media encryption in Web SDK is based on the WebRTC standard, but interoperability with Agora is handled using Agora’s encryption engine, and the encryption key is passed securely to the Web SDK servers through APIs. The key is required because the Agora edge server converts from WebRTC protocols to Agora’s protocol and allows interoperability with Native SDK. A similar situation occurs with Cloud Recording, Content Moderation, and other services, where RESTful APIs are used to securely pass the key for the channel.

### Level 4 - Network geofencing [#level-4---network-geofencing]
To conform to the laws and regulations of different countries and regions, the Agora Video SDK and the Signaling SDK support network geofencing, which limits the transmission of data to within a specified region.
These SDKs support network geofencing in the following regions: global (default), North America, Europe, Asia (excluding Mainland China), Japan, India, and Mainland China. Once a customer specifies a region using geofencing, no audio, video, or message can access Agora servers outside that region.
### Security best practice checklist [#security-best-practice-checklist]
Use this list to quickly check what measures you have or have not taken to best protect the security of your app and users:
1. [Enable Token-based authentication](/en/introduction/projects#manage-app-certificates) on [Agora Console](https://console.agora.io/v2).
2. Disable *No certificate* in your project management page. Once this is done, your app authenticates users with Tokens only.
3. [Deploy a Token server](/en/realtime-media/video/build/authenticate-users/deploy-token-server) in your backend services.
4. Protect the Token server and only allow the app backend server to connect to the Token server.
5. Set the Token expiration time to a reasonable value. See [Deploy a Token server](/en/realtime-media/video/build/authenticate-users/deploy-token-server).
6. If needed, contact [support@agora.io](mailto\:support@agora.io) to enable Token privilege and set Token per role.
7. For additional security, work with the Agora SA team if needed to customize the Token server and modify the Token join privilege to a short time, such as 5 minutes. This is more advanced and recommended after app development is completed.
8. Channel encryption key management: Generate a random key (secret) per channel at the time of creating the channel; do not reuse encryption keys.
9. Pass the channel encryption key (secret) securely outside the Agora engine to authenticated endpoints that are allowed to join the channel.
10. For additional security, you can also do the following:
1. Set the “Agora channel name” to a one-time random string managed by your app.
2. Set the Agora `uid` to a one-time numeric ID that is mapped to the user on the app level. Do not use users' real IDs in your app as the Agora `uid`.
## Agora bug bounty program [#agora-bug-bounty-program]
Agora is always looking to better protect our system and customers. Therefore, we invite security researchers to report any bugs or vulnerabilities they discover to us.
Once we receive notice of a bug or vulnerability, Agora customer service and security teams will respond quickly to address the issue.
In addition to our gratitude, those who report a vulnerability may be eligible for a monetary “bounty” based on the risk associated with the vulnerability and the importance of the affected system.
### System importance classifications [#system-importance-classifications]
The following table shows system importance classifications (in descending order), along with some example Agora assets:
| Classification | Asset Examples |
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Core system | Agora SDRTN®, the mail system, and official websites such as [www.agora.io](http://www.agora.io/), [sso.agora.io](http://sso.agora.io/), and `api.agora.io` |
| General system | Forums and the Developer Portal ([docs.agora.io](https://docs.agora.io/)) |
| Fringe system | Test sites |
### Vulnerability severity classifications [#vulnerability-severity-classifications]
The following list outlines detailed examples of how various vulnerabilities would be classified:
**Critical**
1. Unauthorized system privileges
Examples include command injection (execution), code injection (execution), web shell execution, SQL injection, and buffer overflow that gains system privileges on the core system.
2. Direct denial of service
Examples include actions that make service unavailable, reduce service quality, and so on.
3. Sensitive information leakage
Examples include SQL injection of the core database (identity, order), unauthorized disclosure of sensitive information relating to a user, product order, or payment method, and so on.
4. Serious logic design flaws and process defects
Examples include the ability to send batches of fraudulent messages, account consumption through a business interface, and large-scale modification of account passwords, and so on.
**High**
1. Sensitive information leakage
Examples include unauthorized disclosure of sensitive information relating to source code, hardcoded passwords, and so on.
2. Unauthorized access to sensitive information
Examples include bypassing authentication or backend password checks, leading to unauthorized access to sensitive intranet information.
3. Unauthorized sensitive operations
Examples include manipulating important information without authorization, such as orders, major business configurations, and so on.
**Medium**
1. Vulnerabilities that require interaction and affect users
Examples include stored XSS (cross-site scripting).
2. General unauthorized operations
Examples include incorrect direct object references, unauthorized access to orders, unauthorized access to user information, and so on.
3. General information leakage
Examples include client-side stored plaintext passwords, system path traversal, and so on.
4. General logic design flaws and process defects
**Low**
1. Local denial of service vulnerabilities, CSRF (cross-site request forgery), reflected XSS, and so on.
2. Minor information leakage, such as path information, SVN information, exception information, the local SQL injection of a client-side application (limited to database name, field name, log print), and so on.
3. Vulnerabilities that are difficult to exploit but still have security implications, such as plaintext transmission of passwords.
**Info**
1. Exposure of software banners, internal IP addresses, some public email addresses or phone numbers, and so on.
2. Using outdated versions of a system, supporting outdated versions of encryption protocols, such as SSL (Secure Sockets Layer) or TLS (Transport Layer Security) 1.0, supporting low-strength encryption algorithms, and so on.
### Bug Bounties [#bug-bounties]
Please report any potential risks of Agora services that you may notice to [security@agora.io](mailto\:security@agora.io). Any bounties awarded will conform roughly to the following ranges (based on the severity and system location of the bug or vulnerability, payable in USD):
* Critical: $300 to $5000
* High: $200 to $2000
* Medium: $150
* Low: $50
* Info: $0
Note: Agora reserves the sole right to determine any reward amount given.
# Start with AI (/en/introduction/start-with-ai)
Use this page when you want your coding assistant to help you get started without guessing. The steps are simple: install the CLI, add the Agora skill, then give the assistant a prompt with the product, platform, language, and goal.
If you also want live docs lookup while the assistant works, pair [Agora Skills](/en/introduction/agora-skills) with [Agora MCP](/en/introduction/agora-mcp). Skills handles workflow guidance, and MCP handles current documentation.
## Quick start [#quick-start]
### 1. Install CLI [#1-install-cli]
macOS and Linux
Windows (PowerShell)
```bash
curl -fsSL https://agoraio.github.io/cli/install.sh | sh -s -- --add-to-path
agora --help
```
```powershell
irm https://agoraio.github.io/cli/install.ps1 | iex
agora --help
```
If your execution policy blocks inline scripts, download `install.ps1` and run:
```powershell
powershell -ExecutionPolicy Bypass -File .\install.ps1
```
After installation, confirm `agora --help` works in the same shell.
### 2. Add skill [#2-add-skill]
Install [Agora Skills](/en/introduction/agora-skills) so your assistant can choose the right Agora workflow, starter, and setup sequence before it edits code.
Skills CLI
Manual installation
```bash
npx skills add AgoraIO/skills
```
Clone the repository and point your coding assistant to the Agora skill files:
```bash
git clone https://github.com/AgoraIO/skills.git ~/agora-skills
```
Use `skills/agora/` as the directory entry point, or load `SKILL.md` directly if your tool expects a single root file.
If your tool prefers local instruction files, point it at `skills/agora/` or the top-level `SKILL.md`.
If you only want docs lookup and not workflow guidance, install [Agora MCP](/en/introduction/agora-mcp) instead. Many teams use both.
### 3. Prompt [#3-prompt]
Now tell the assistant what you want it to build and how to approach the setup. A strong prompt includes the Agora product, the target platform, the language or starter preference, and the end goal.
```text
Use Agora Skills to help me build a web-based conversational AI demo in Python.
```
From there, let the assistant choose the starter, run `agora login`, initialize the project, and explain any environment values it writes locally.
## What success looks like [#what-success-looks-like]
You are on the right path when the assistant can:
* choose an official starter instead of inventing one
* explain why that starter matches your product and platform
* use the Agora CLI for setup instead of ad hoc manual steps
* stay grounded in current Agora docs when platform details matter
# Support (/en/introduction/support)
## Submit a ticket [#submit-a-ticket]
To ask Agora Support a question:
1. Log in to Agora Console.
2. Click **Get Support** > **Create Support Ticket**.
3. Type your question or keywords to see whether the issue has already been answered.
4. If you cannot find an answer, select a category and submit a ticket to customer support.
You can track ticket status under **Tickets**.
## Status page [#status-page]
Agora Status Page provides up-to-date information about product and service status, including:
* historical service stability for the past 90 days
* real-time quality data for the past 24 hours
* maintenance events and disruptions
* RSS event subscriptions
You can access the status page directly at [status.agora.io](https://status.agora.io/).
The status dashboard currently provides status information for:
* Real-Time Communication
* Cloud Recording
* Agora Chat
* Interactive Whiteboard
# Usage Analytics (/en/introduction/usage-analytics)

Agora Analytics tracks and analyzes the usage, quality, and performance of real-time voice, video, live streaming, chat, and other Agora products.
## Features [#features]
* **Call Inspector**: identify, analyze, and respond to performance quality issues with detailed call and user metrics.
* **Data Insights**: periodic call usage and quality statistics.
* **Real Time Monitoring**: visualized data for multiple call metrics in real time.
* **Alert notifications**: alerts when abnormal metrics or events are detected.
* **RESTful APIs**: retrieve call statistics and quality metrics for your own application or DataOps workflow.
* **Embedding**: embed Analytics pages in internal web portals using a low-code approach.
## Console usage page [#console-usage-page]
The Console **Usage** page lets you check usage for the past 12 months.
You can:
* Select a time frame and data granularity.
* Filter by all projects or a specific project.
* Select a product.
* Turn on **View Breakdown**.
Only **Admin**, **Product/Operation**, and authorized custom teams can access the **Usage** page.
# Foundational realtime capabilities (/en/realtime-media/foundation-realtime)
## RTC [#rtc]
Owns audio, video, and channel sessions as the realtime base for calling, live streaming, meetings, and classrooms.
## RTM [#rtm]
Provides low-latency, reliable messaging plus state synchronization for channel messages, presence, and workflow coordination.
## Instant Messaging IM [#instant-messaging-im]
Fits broader cloud-chat workloads where a full messaging service is needed beyond lightweight realtime coordination.
## Realtime Transcription & Translation [#realtime-transcription--translation]
Provides ultra-low-latency, high-accuracy speech transcription and translation for captions, multilingual communication, and content understanding.
## RTSA [#rtsa]
Provides realtime media-stream and signaling transport for device-oriented and terminal-style workloads.
# Overview (/en/realtime-media)
Realtime & Media is the capability domain for products that need something to happen live. Use this section when you are deciding how people, devices, backend services, and media streams should connect, stay in sync, and keep working while a session is in progress.
Instead of organizing docs around a single product story, this tab is organized around the functional layers most teams assemble when building a realtime experience: live interaction, session intelligence, media routing, large-scale delivery, and device or server participation.
## What you can build from this section [#what-you-can-build-from-this-section]
### Create live interaction surfaces [#create-live-interaction-surfaces]
Use these docs when you are defining how users join a session, exchange media, coordinate actions, or collaborate in the same live space.
* [Voice & Video](/en/realtime-media/rtc): calls, meetings, co-hosting, interactive live rooms, and media quality control
* [Signaling](/en/realtime-media/rtm): channel messaging, presence, state sync, and realtime coordination
* [Chat](/en/realtime-media/im): persistent and full-featured messaging systems beyond lightweight room coordination
* [Whiteboard](/en/realtime-media/whiteboard): shared visual collaboration inside live sessions
### Understand, capture, and transform session content [#understand-capture-and-transform-session-content]
Use these docs when media inside the session needs to become text, records, composites, or downstream assets.
* [Transcription & Translation](/en/realtime-media/speech-to-text): captions, speech understanding, and multilingual live experiences
* [Cloud Recording](/en/realtime-media/cloud-recording): archive, replay, compliance, QA, and post-session review workflows
* [Transcoding](/en/realtime-media/transcoding): mixing, layout composition, and output transformation
### Bridge realtime sessions with external media systems [#bridge-realtime-sessions-with-external-media-systems]
Use these docs when your product must ingest outside streams, publish session media to other systems, or interoperate with existing streaming infrastructure.
* [Media Push](/en/realtime-media/media-push): send RTC channel media to CDN pipelines or downstream media systems
* [Media Pull](/en/realtime-media/media-pull): bring online media streams into an interactive realtime session
* [RTMP Gateway](/en/realtime-media/rtmp-gateway): connect RTMP-based devices and systems to Agora
### Deliver playback to larger audiences [#deliver-playback-to-larger-audiences]
Use these docs when interactive participation and large-scale viewing are separate concerns in your architecture.
* [Fusion CDN](/en/realtime-media/fusion-cdn): multi-CDN distribution for live playback at audience scale
### Extend the session to devices and backend services [#extend-the-session-to-devices-and-backend-services]
Use these docs when participants are not just mobile or web clients, but also embedded devices, operator consoles, or server-side workers.
* [IoT & Edge](/en/realtime-media/rtsa): device and edge connectivity for cameras, terminals, and embedded endpoints
* [RTC Server SDK](/en/realtime-media/rtc-server-sdk): backend participation in media send, receive, subscribe, and control flows
## How to navigate this tab [#how-to-navigate-this-tab]
* Start with [Voice & Video](/en/realtime-media/rtc), [Signaling](/en/realtime-media/rtm), [Chat](/en/realtime-media/im), or [Whiteboard](/en/realtime-media/whiteboard) when your main question is how users interact inside a live session.
* Start with [Transcription & Translation](/en/realtime-media/speech-to-text), [Cloud Recording](/en/realtime-media/cloud-recording), or [Transcoding](/en/realtime-media/transcoding) when your main question is what should happen to the media during or after the session.
* Start with [Media Push](/en/realtime-media/media-push), [Media Pull](/en/realtime-media/media-pull), [RTMP Gateway](/en/realtime-media/rtmp-gateway), or [Fusion CDN](/en/realtime-media/fusion-cdn) when your main question is how to move media across systems or out to larger audiences.
* Start with [IoT & Edge](/en/realtime-media/rtsa) or [RTC Server SDK](/en/realtime-media/rtc-server-sdk) when your main question is how devices or backend services participate in the realtime workflow.
## Common reading paths [#common-reading-paths]
* Interactive live streaming with large audience playback: [Voice & Video](/en/realtime-media/rtc) -> [Media Push](/en/realtime-media/media-push) -> [Fusion CDN](/en/realtime-media/fusion-cdn)
* Meeting archive and searchable records: [Voice & Video](/en/realtime-media/rtc) -> [Cloud Recording](/en/realtime-media/cloud-recording) -> [Transcription & Translation](/en/realtime-media/speech-to-text)
* External stream into an interactive session: [Media Pull](/en/realtime-media/media-pull) or [RTMP Gateway](/en/realtime-media/rtmp-gateway) -> [Voice & Video](/en/realtime-media/rtc)
* Smart device connectivity with backend media control: [IoT & Edge](/en/realtime-media/rtsa) -> [RTC Server SDK](/en/realtime-media/rtc-server-sdk)
# Media processing and distribution (/en/realtime-media/media-processing-and-distribution)
## Recording [#recording]
Recording captures live interaction content into your storage flow for archive, replay, review, and compliance-oriented workflows.
### Cloud Recording [#cloud-recording]
Fits teams that want a managed recording path from Agora.
### Local Server Recording [#local-server-recording]
Fits teams that need self-managed recording servers and finer-grained control over the recording workflow.
## Media Push [#media-push]
Pushes interactive channel content into CDN or broadcast-style distribution paths.
## Media Pull [#media-pull]
Brings external online media streams into an existing realtime scenario.
## Transcoding [#transcoding]
Transcoding handles media transformation and composition for live streaming and multi-layout output scenarios.
### Cloud Transcoding [#cloud-transcoding]
Fits teams that need hosted stream composition and output transformation.
## RTMP Gateway [#rtmp-gateway]
Bridges standard live protocol flows with realtime interaction systems.
## Fusion CDN Live Streaming [#fusion-cdn-live-streaming]
Optimizes end-to-end playback through multi-CDN scheduling and distribution.
Additional note: the broader Agora content system also includes PPT Conversion. For this IA pass, it can stay as an extension of transcoding rather than a first-class left-nav item.
# RTC overview (/en/realtime-media/overview)
## Build live interaction [#build-live-interaction]
## Process session content [#process-session-content]
## Connect external media [#connect-external-media]
## Deliver to audiences [#deliver-to-audiences]
# Server-side and extensions (/en/realtime-media/server-and-extensions)
## RTC Server SDK [#rtc-server-sdk]
Lets the backend participate directly in RTC media send, receive, and control flows.
## SDK extension plugins [#sdk-extension-plugins]
Support capability enhancement and customized audio-video effects on top of the existing SDK stack.
## Marketplace [#marketplace]
Provides a faster path to selecting, purchasing, and connecting external realtime modules into your product.
# Set up service and credentials (/en/realtime-media/setup-service-and-credentials)
## Why this always comes first [#why-this-always-comes-first]
No matter whether you end up building AI, RTC, RTM, recording, or transcoding, the first step is rarely "write code." It is usually confirming Console access, project state, App ID, credentials, and authentication.
## What you should prepare [#what-you-should-prepare]
* project and App ID
* customer ID and customer secret
* token generation strategy
* service enablement status
* the boundary between test and production environments
## Recommended practice [#recommended-practice]
Prepare the control plane and credential flow first, then move into product-specific docs. That prevents expensive rework when permissions, quotas, or auth assumptions diverge later.
# API reference (/en/api-reference/api-ref)
# Channel Management REST API (/en/api-reference/api-ref/iot-channel-management-rest-api)
In addition to the SDK that you integrate into the app client, Agora provides server-side RESTful APIs to manage real-time channels.
The IoT Channel Management REST API uses the shared RTC REST API reference. Use these APIs to query channel information, manage user privileges, and query Agora Notifications service IP addresses.
## REST API reference [#rest-api-reference]
* [RTC REST API overview](/en/api-reference/api-ref/rtc)
* [How to call RESTful APIs](/en/api-reference/api-ref/rtc/how-to-call-api)
* [RESTful authentication](/en/api-reference/api-ref/rtc/authentication)
* [Response status codes](/en/api-reference/api-ref/rtc/response-status-codes)
## Endpoints [#endpoints]
**Channel information**
* [Query the channel list](/en/api-reference/api-ref/rtc/query-channel-list)
* [Query the user list](/en/api-reference/api-ref/rtc/query-user-list)
* [Query the host list](/en/api-reference/api-ref/rtc/query-host-list)
* [Query the user status](/en/api-reference/api-ref/rtc/query-user-status)
**User privilege banning**
* [Create a banning rule](/en/api-reference/api-ref/rtc/create-ban-rule)
* [Delete a banning rule](/en/api-reference/api-ref/rtc/delete-ban-rule)
* [Get the banning rule list](/en/api-reference/api-ref/rtc/get-ban-rule-list)
* [Update the banning rule expiration](/en/api-reference/api-ref/rtc/update-ban-expiration)
**Message notification service**
* [Query the IP address](/en/api-reference/api-ref/rtc/query-ip-address)
## Supporting topics [#supporting-topics]
* [Ban user privileges best practices](/en/api-reference/api-ref/rtc/ban-user-privileges-best-practices)
* [Ensure service reliability](/en/api-reference/api-ref/rtc/ensure-service-reliability)
* [Channel event types](/en/api-reference/api-ref/rtc/channel-event-types)
# Fastboard API (/en/api-reference/api-ref/uikit-sdk)
This page provides the API reference for the Fastboard SDK.
<_PlatformTabsGroup groupMode="structured" canonicalPlatform="web" platforms="["android","ios","web"]" showTabs="true">
<_PlatformPanel platform="android">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="android" />
## Understand the tech [#understand-the-tech]
This section provides the API reference for the Fastboard SDK on Android.
## Reference [#reference]
## FastboardView class [#fastboardview-class]
### getFastboard [#getfastboard]
```java
public Fastboard getFastboard()
```
Get the `Fastboard` object.
The Fastboard SDK does not support initializing a `Fastboard` instance directly. To get the `Fastboard` object, you need to add the `FastboardView` object to the app's layout, and then call the `getFastboard` method.
## Fastboard class [#fastboard-class]
### createFastRoom [#createfastroom]
```java
public FastRoom createFastRoom(FastRoomOptions roomOptions)
```
### preloadWhiteboardView [#preloadwhiteboardview]
```java
public static void preloadWhiteboardView() {
WhiteboardViewManager.get().preload();
}
```
### destroy [#destroy]
```java
public void destroy()
```
### setConfig [#setconfig]
```java
public static void setConfig(FastboardConfig config)
```
### FastRoomOptions [#fastroomoptions]
```java
public class FastRoomOptions {
private final String appId;
private final String uuid;
private final String token;
private final String uid;
private final boolean writable;
private final FastRegion fastRegion;
}
```
### FastRegion [#fastregion]
* `CN_HZ`
* `US_SV`
* `SG`
* `IN_MUM`
* `EU`
### FastUserPayload [#fastuserpayload]
```java
public class FastUserPayload {
private final String nickName;
private final String avatar;
}
```
### FastboardConfig [#fastboardconfig]
```java
public class FastboardConfig {
private final boolean enablePreload;
private final int preloadCount;
private final boolean autoPreload;
}
```
## FastRoom class [#fastroom-class]
### join \[1/2] [#join-12]
```java
public void join()
```
### join \[2/2] [#join-22]
```java
public void join(@Nullable OnRoomReadyCallback onRoomReadyCallback)
```
### setWritable [#setwritable]
```java
public void setWritable(boolean writable)
```
### registerApp [#registerapp]
```java
public void registerApp(FastRegisterAppParams params, FastResult result)
```
### insertImage [#insertimage]
```java
public void insertImage(String url, int width, int height)
```
### insertVideo [#insertvideo]
```java
public void insertVideo(String url, String title)
```
### insertStaticDoc [#insertstaticdoc]
```java
public void insertStaticDoc(DocPage[] pages, String title, FastResult result)
```
### insertPptx [#insertpptx]
```java
public void insertPptx(String taskUuid, String prefixUrl, String title, FastResult result)
```
### insertDocs [#insertdocs]
```java
public void insertDocs(FastInsertDocParams params, FastResult result)
```
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="ios">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="ios" />
## Understand the tech [#understand-the-tech-1]
This section provides the API reference for the Fastboard SDK on iOS.
## Reference [#reference-1]
## Fastboard class [#fastboard-class-1]
### createFastRoom [#createfastroom-1]
```swift
public class func createFastRoom(withFastRoomConfig config: FastRoomConfiguration)
```
Create a `FastRoom` instance.
### FastRoomConfiguration [#fastroomconfiguration]
```swift
public init(appIdentifier: String,
roomUUID: String,
roomToken: String,
region: Region,
userUID: String,
userPayload: FastUserPayload? = nil)
```
### Region [#region]
* `CN`
* `US`
* `SG`
* `IN`
* `EU`
### FastUserPayload [#fastuserpayload-1]
```swift
public class FastUserPayload: NSObject {
let nickName: String?
let avatar: String?
}
```
## FastRoom class [#fastroom-class-1]
### joinRoom [#joinroom]
```swift
public func joinRoom(completionHandler: ((Result)->Void)? = nil)
```
### disconnectRoom [#disconnectroom]
```swift
public func disconnectRoom()
```
### insertImg [#insertimg]
```swift
public func insertImg(_ src: URL, imageSize: CGSize)
```
### insertMedia [#insertmedia]
```swift
public func insertMedia(_ src: URL, title: String, completionHandler: ((String)->Void)? = nil)
```
### insertPptx [#insertpptx-1]
```swift
public func insertPptx(
uuid: String,
url: String,
title: String,
completionHandler: ((String)->Void)? = nil
)
```
### insertStaticDocument [#insertstaticdocument]
```swift
public func insertStaticDocument(_ pages: [WhitePptPage],
title: String,
completionHandler: ((String)->Void)? = nil)
```
### followSystemPencilBehavior [#followsystempencilbehavior]
```swift
public static var followSystemPencilBehavior
```
## FastRoomThemeManager class [#fastroomthememanager-class]
### apply [#apply]
```swift
public func apply(_ theme: FastRoomThemeAsset)
```
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="web">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="web" />
## Understand the tech [#understand-the-tech-2]
This section provides the API reference for the Fastboard SDK on Web.
## Reference [#reference-2]
## createFastboard [#createfastboard]
```typescript
createFastboard(options: FastboardOptions): Promise;
```
## createUI [#createui]
```typescript
createUI(app?: FastboardApp | null, div?: Element): UI;
```
## createReplayUI [#createreplayui]
```typescript
createReplayUI(player?: FastboardPlayer | null, div?: Element): ReplayUI;
```
### canOperate [#canoperate]
```typescript
get canOperate(): boolean
```
## mount [#mount]
```typescript
mount(app: FastboardApp, div: HTMLDivElement, options?: MountProps): {
update(props?: MountProps | undefined): void;
destroy(): void;
};
```
## dispatchDocsEvent [#dispatchdocsevent]
```typescript
export function dispatchDocsEvent(
fastboard: FastboardApp | WindowManager,
event: "prevPage" | "nextPage" | "prevStep" | "nextStep" | "jumpToPage",
options: DocsEventOptions = {}
)
```
## FastboardApp class [#fastboardapp-class]
### undo [#undo]
```typescript
undo(): void
```
### redo [#redo]
```typescript
redo(): void
```
### moveCamera [#movecamera]
```typescript
moveCamera(camera: Partial): void
```
### moveCameraToContain [#movecameratocontain]
```typescript
moveCameraToContain(rect: Rectangle): void;
```
### setAppliance [#setappliance]
```typescript
setAppliance(appliance: ApplianceNames, shape?: ShapeType): void;
```
### setStrokeWidth [#setstrokewidth]
```typescript
setStrokeWidth(strokeWidth: number): void
```
### setStrokeColor [#setstrokecolor]
```typescript
setStrokeColor(strokeColor: Color): void
```
### setTextColor [#settextcolor]
```typescript
setTextColor(textColor: Color): void
```
### setTextSize [#settextsize]
```typescript
setTextSize(textSize: number): void
```
### insertImage [#insertimage-1]
```typescript
async insertImage(url: string, crossOrigin?: boolean | string)
```
### insertMedia [#insertmedia-1]
```typescript
insertMedia(title: string, src: string): Promise
```
### insertDocs \[1/2] [#insertdocs-12]
```typescript
insertDocs(title: string, response: ProjectorResponse): Promise;
```
### insertDocs \[2/2] [#insertdocs-22]
```typescript
insertDocs(params: InsertDocsParams): Promise;
```
### removePage [#removepage]
```typescript
removePage(index?): Promise
```
### jumpPage [#jumppage]
```typescript
jumpPage(index: number)
```
<_PlatformProcessedMarker close="true" />
# FAQ (/en/api-reference/faq)
# Optimize audio quality (/en/ai/best-practices/audio-setup)
In real-time audio interactions, the rhythm, continuity, and intonation of conversations between humans and AI often differ from those between humans. To improve the AI–human conversation experience, it's important to optimize audio settings.
When using the Android, iOS, or Web RTC SDK with the Conversational AI Engine, follow the best practices in this guide to improve conversation fluency and reliability, especially in complex network environments.
## Server configuration [#server-configuration]
When calling the server API to create a conversational AI agent, use the default values for audio-related parameters to ensure the best audio experience.
## Client configuration [#client-configuration]
To configure the client app, implement the following:
### Integrate the required dynamic libraries [#integrate-the-required-dynamic-libraries]
For the best Conversational AI Engine audio experience, integrate and load the following dynamic libraries in your project:
Android
iOS
Web
* AI noise suppression plugin: `libagora_ai_noise_suppression_extension.so`
* AI echo cancellation plug-in: `libagora_ai_echo_cancellation_extension.so`
For integration details, refer to [App size optimization](/en/realtime-media/voice/build/optimize-and-operate/app-size-optimization/android).
* AI noise suppression plugin: `AgoraAiNoiseSuppressionExtension.xcframework`
* AI echo cancellation plug-in: `AgoraAiEchoCancellationExtension.xcframework`
For integration details, refer to [App size optimization](/en/realtime-media/voice/build/optimize-and-operate/app-size-optimization/ios).
* Integrate the `agora-extension-ai-denoiser` extension. Refer to [AI Noise Suppression](/en/realtime-media/voice/build/enhance-the-audio-experience/ai-noise-suppression/web).
Info
Optimizing audio uses AI Noise Suppression, which is a paid feature.
## Optimize audio for optimal performance [#optimize-audio-for-optimal-performance]
You can optimize audio settings in the following ways:
* **(Recommended) Use the Toolkit APIs**
Supported in Video/Voice SDK version 4.5.1 and above.
* **Use the Video/Voice SDK APIs directly**
Supported in SDK version 4.3.1 and above.
### Use the toolkit APIs [#use-the-toolkit-apis]
In this solution, you use the toolkit APIs to optimize audio settings.
Android
iOS
Web
1. **Integrate the toolkit**
Copy the [`convoaiApi`](https://github.com/AgoraIO-Community/Conversational-AI-Demo/tree/main/Android/scenes/convoai/src/main/java/io/agora/scene/convoai/convoaiApi) folder to your project and import the toolkit before calling the toolkit API.
2. **Create a toolkit instance**
```kotlin
val config = ConversationalAIAPIConfig(
rtcEngine = rtcEngineInstance,
rtmClient = rtmClientInstance,
enableLog = true
)
val api = ConversationalAIAPIImpl(config)
```
3. **Set optimal audio settings**
Before joining the RTC channel, call `loadAudioSettings()` to apply the optimal audio parameters.
```kotlin
api.loadAudioSettings()
rtcEngine.joinChannel(token, channelName, null, userId)
```
4. **Release resources**
```kotlin
api.destroy()
```
1. **Integrate the toolkit**
Copy the [`ConversationalAIAPI`](https://github.com/AgoraIO-Community/Conversational-AI-Demo/tree/main/iOS/Scenes/ConvoAI/ConvoAI/ConvoAI/Classes/ConversationalAIAPI) folder to your project and import the toolkit before calling the toolkit APIs.
2. **Create a toolkit instance**
```swift
let config = ConversationalAIAPIConfig(
rtcEngine: rtcEngine,
rtmEngine: rtmEngine,
enableLog: true
)
convoAIAPI = ConversationalAIAPIImpl(config: config)
```
3. **Set optimal audio settings**
Before joining the RTC channel, call `loadAudioSettings()` to apply the optimal audio parameters.
```swift
convoAIAPI.loadAudioSettings()
rtcEngine.joinChannel(rtcToken: token, channelName: channelName, uid: uid, isIndependent: independent)
```
4. **Release resources**
```swift
convoAIAPI.destroy()
```
* **Integrate the transcript processing toolkit**
Copy the [`conversational-ai-api`](https://github.com/AgoraIO-Community/Conversational-AI-Demo/tree/main/Web/Scenes/VoiceAgent/src/conversational-ai-api) file to your project and import the toolkit before calling its API. Refer to [Folder structure](#folder-structure) to understand the role of each file.
### Use the SDK APIs [#use-the-sdk-apis]
In this solution, you use the Voice/Video SDK to optimize audio settings.
#### Set audio parameters [#set-audio-parameters]
The settings in this section apply to Video/Voice SDK versions 4.3.1 and above. If you are using an earlier version, upgrade to version 4.5.1 or above or [Contact Technical Support](mailto\:support@agora.io).
Android
iOS
Web
For the best conversational AI audio experience, apply the following settings:
1. **Set the audio scenario**: When initializing the engine, set the audio scenario to the AI client scenario. You can also set the scenario before joining a channel by calling the `setAudioScenario` method.
2. **Configure audio parameters**: Call `setParameters` before joining a channel and whenever the `onAudioRouteChanged` callback is triggered. This configuration sets audio 3A plug-ins (acoustic echo cancellation, noise suppression, and automatic gain control), the audio sampling rate, the audio processing mode, and other settings. For recommended parameter values, refer to the sample code.
Info
Since Video/Voice SDK versions 4.3.1 to 4.5.0 do not support the AI client audio scenario, set the scenario to `AUDIO_SCENARIO_CHORUS` to improve the audio experience. However, the audio experience cannot be aligned with versions 4.5.1 and above. To get the best audio experience, upgrade the SDK to version 4.5.1 or higher.
The following sample code defines a `setAudioConfigParameters` function to configure audio parameters. Call this function before joining a channel and whenever the audio route changes.
```kotlin
private var rtcEngine: RtcEngineEx? = null
private var mAudioRouting = Constants.AUDIO_ROUTE_DEFAULT
// highlight-start
// Set audio configuration parameters
private fun setAudioConfigParameters(routing: Int) {
mAudioRouting = routing
rtcEngine?.apply {
setParameters("{\"che.audio.aec.split_srate_for_48k\":16000}")
setParameters("{\"che.audio.sf.enabled\":true}")
setParameters("{\"che.audio.sf.stftType\":6}")
setParameters("{\"che.audio.sf.ainlpLowLatencyFlag\":1}")
setParameters("{\"che.audio.sf.ainsLowLatencyFlag\":1}")
setParameters("{\"che.audio.sf.procChainMode\":1}")
setParameters("{\"che.audio.sf.nlpDynamicMode\":1}")
if (routing == Constants.AUDIO_ROUTE_HEADSET // 0
|| routing == Constants.AUDIO_ROUTE_EARPIECE // 1
|| routing == Constants.AUDIO_ROUTE_HEADSETNOMIC // 2
|| routing == Constants.AUDIO_ROUTE_BLUETOOTH_DEVICE_HFP // 5
|| routing == Constants.AUDIO_ROUTE_BLUETOOTH_DEVICE_A2DP) { // 10
setParameters("{\"che.audio.sf.nlpAlgRoute\":0}")
} else {
setParameters("{\"che.audio.sf.nlpAlgRoute\":1}")
}
setParameters("{\"che.audio.sf.ainlpModelPref\":10}")
setParameters("{\"che.audio.sf.nsngAlgRoute\":12}")
setParameters("{\"che.audio.sf.ainsModelPref\":10}")
setParameters("{\"che.audio.sf.nsngPredefAgg\":11}")
setParameters("{\"che.audio.agc.enable\":false}")
}
}
// highlight-end
// Create and initialize the RTC engine
fun createRtcEngine(rtcCallback: IRtcEngineEventHandler): RtcEngineEx {
val config = RtcEngineConfig()
config.mContext = AgentApp.instance()
config.mAppId = ServerConfig.rtcAppId
config.mChannelProfile = Constants.CHANNEL_PROFILE_LIVE_BROADCASTING
// highlight-start
// Set the audio scene to AI dialogue scene (supported by 4.5.1 and above)
// Version 4.3.1 ~ 4.5.0 is set to chorus scene AUDIO_SCENARIO_CHORUS
config.mAudioScenario = Constants.AUDIO_SCENARIO_AI_CLIENT
// Register audio route change callback
config.mEventHandler = object : IRtcEngineEventHandler() {
override fun onAudioRouteChanged(routing: Int) {
super.onAudioRouteChanged(routing)
// Set audio related parameters
setAudioConfigParameters(routing)
}
}
// highlight-end
try {
rtcEngine = (RtcEngine.create(config) as RtcEngineEx).apply {
// highlight-start
// Load the audio plugin
loadExtensionProvider("ai_echo_cancellation_extension")
loadExtensionProvider("ai_noise_suppression_extension")
// highlight-end
}
} catch (e: Exception) {
Log.e("CovAgoraManager", "createRtcEngine error: $e")
}
return rtcEngine!!
}
// Join the channel
fun joinChannel(rtcToken: String, channelName: String, uid: Int, isIndependent: Boolean = false) {
// highlight-start
// Initialize audio configuration parameters
setAudioConfigParameters(mAudioRouting)
// highlight-end
// Configure channel options and join the channel
val options = ChannelMediaOptions()
options.clientRoleType = CLIENT_ROLE_BROADCASTER
options.publishMicrophoneTrack = true
options.publishCameraTrack = false
options.autoSubscribeAudio = true
options.autoSubscribeVideo = false
val ret = rtcEngine?.joinChannel(rtcToken, channelName, uid, options)
}
```
For the best conversational AI audio experience, apply the following settings:
1. **Set the audio scenario**: When initializing the engine, set the audio scenario to the AI client scenario. You can also set the scenario before joining a channel by calling the `setAudioScenario` method.
2. **Configure audio parameters**: Call `setParameters` before joining a channel and whenever the `rtcEngine:didAudioRouteChanged:` callback is triggered. This configuration sets audio 3A plug-ins (acoustic echo cancellation, noise suppression, and automatic gain control), the audio sampling rate, the audio processing mode, and other settings. For recommended parameter values, refer to the sample code.
Info
Since Video/Voice SDK versions 4.3.1 to 4.5.0 do not support the AI client audio scenario, set the scenario to `AgoraAudioScenarioChorus` to improve the audio experience. However, the audio experience cannot be aligned with versions 4.5.1 and above. To get the best audio experience, upgrade the SDK to version 4.5.1 or higher.
The following sample code defines a `setAudioConfigParameters` function to configure audio parameters. Call this function before joining a channel and whenever the audio route changes.
```swift
class RTCManager: NSObject {
private var rtcEngine: AgoraRtcEngineKit!
private var audioDumpEnabled: Bool = false
private var audioRouting = AgoraAudioOutputRouting.default
// highlight-start
// Set audio related parameters
private func setAudioConfigParameters(routing: AgoraAudioOutputRouting) {
audioRouting = routing
rtcEngine.setParameters("{\"che.audio.aec.split_srate_for_48k\":16000}")
rtcEngine.setParameters("{\"che.audio.sf.enabled\":true}")
rtcEngine.setParameters("{\"che.audio.sf.stftType\":6}")
rtcEngine.setParameters("{\"che.audio.sf.ainlpLowLatencyFlag\":1}")
rtcEngine.setParameters("{\"che.audio.sf.ainsLowLatencyFlag\":1}")
rtcEngine.setParameters("{\"che.audio.sf.procChainMode\":1}")
rtcEngine.setParameters("{\"che.audio.sf.nlpDynamicMode\":1}")
if routing == .headset ||
routing == .earpiece ||
routing == .headsetNoMic ||
routing == .bluetoothDeviceHfp ||
routing == .bluetoothDeviceA2dp {
rtcEngine.setParameters("{\"che.audio.sf.nlpAlgRoute\":0}")
} else {
rtcEngine.setParameters("{\"che.audio.sf.nlpAlgRoute\":1}")
}
rtcEngine.setParameters("{\"che.audio.sf.ainlpModelPref\":10}")
rtcEngine.setParameters("{\"che.audio.sf.nsngAlgRoute\":12}")
rtcEngine.setParameters("{\"che.audio.sf.ainsModelPref\":10}")
rtcEngine.setParameters("{\"che.audio.sf.nsngPredefAgg\":11}")
rtcEngine.setParameters("{\"che.audio.agc.enable\":false}")
}
// highlight-end
}
extension RTCManager: RTCManagerProtocol {
func createRtcEngine(delegate: AgoraRtcEngineDelegate) -> AgoraRtcEngineKit {
let config = AgoraRtcEngineConfig()
config.appId = AppContext.shared.appId
config.channelProfile = .liveBroadcasting
// highlight-start
// Set the audio scene to AI dialogue scene (supported by 4.5.1 and above)
// Versions 4.3.1 ~ 4.5.0 support chorus scenes .chorus
config.audioScenario = .aiClient
rtcEngine = AgoraRtcEngineKit.sharedEngine(with: config, delegate: delegate)
// Register audio route change callback
rtcEngine.addDelegate(self)
// highlight-end
return rtcEngine
}
func joinChannel(rtcToken: String, channelName: String, uid: String) {
// highlight-start
// Initialize audio configuration parameters
setAudioConfigParameters(routing: audioRouting)
// highlight-end
// Configure channel options and join the channel
let options = AgoraRtcChannelMediaOptions()
options.clientRoleType = .broadcaster
options.publishMicrophoneTrack = true
options.publishCameraTrack = false
options.autoSubscribeAudio = true
options.autoSubscribeVideo = false
let ret = rtcEngine.joinChannel(byToken: rtcToken, channelId: channelName, uid: UInt(uid) ?? 0, mediaOptions: options)
}
}
// highlight-start
// Implement the AgoraRtcEngineDelegate interface to handle audio route change callbacks
extension RTCManager: AgoraRtcEngineDelegate {
public func rtcEngine(_ engine: AgoraRtcEngineKit, didAudioRouteChanged routing: AgoraAudioOutputRouting) {
setAudioConfigParameters(routing: routing)
}
}
// highlight-end
```
Integrate the `agora-extension-ai-denoiser` extension. Refer to [AI Noise Suppression](/en/realtime-media/voice/build/enhance-the-audio-experience/ai-noise-suppression/web).
Info
Use Web SDK version 4.15.0 or later.
## Reference [#reference]
This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.
### Sample project [#sample-project]
Refer to the following open-source sample code to set audio-related parameters.
Android
iOS
* [`CovRtcManager.kt`](https://github.com/AgoraIO-Community/Conversational-AI-Demo/blob/main/Android/scenes/convoai/src/main/java/io/agora/scene/convoai/rtc/CovRtcManager.kt)
* [`RTCManager.swift`](https://github.com/AgoraIO-Community/Conversational-AI-Demo/blob/main/iOS/Scenes/ConvoAI/ConvoAI/ConvoAI/Classes/Manager/RTCManager.swift)
### Folder structure [#folder-structure]
Android
iOS
Web
* `IConversationalAIAPI.kt`: API interface and related data structures and enumerations
* `ConversationalAIAPIImpl.kt`: ConversationalAI API main implementation logic
* `ConversationalAIUtils.kt`: Tool functions and event callback management
* `subRender/`
* `v3/`: Transcript module
* `TranscriptionController.kt`: Transcript Controller
* `MessageParser.kt`: Message Parser
* `ConversationalAIAPI.swift`: API interface and related data structures and enumerations
* `ConversationalAIAPIImpl.swift`: ConversationalAI API main implementation logic
* `Transcription/`
* `TranscriptionController.swift`: Transcript Controller
* `index.ts`: API Class
* `type.ts`: API interface and related data structures and enumerations
* `utils/`
* `index.ts`: API utility functions
* `events.ts`: Event management class, which can be extended to easily implement event monitoring and broadcasting
* `sub-render.ts`: Transcript module
### API reference [#api-reference]
Android
iOS
* SDK
* `setAudioScenario`
* `setParameters`
* `onAudioRouteChanged`
* Toolkit
* [`loadAudioSettings`](/en/api-reference/api-ref/conversational-ai/client-toolkit/android#loadaudiosettings)
* [`destroy`](/en/api-reference/api-ref/conversational-ai/client-toolkit/android#destroy)
* SDK
* `setAudioScenario`
* `setParameters`
* `rtcEngine:didAudioRouteChanged:`
* Toolkit
* [`loadAudioSettings`](/en/api-reference/api-ref/conversational-ai/client-toolkit/ios#loadaudiosettings12)
* [`destroy`](/en/api-reference/api-ref/conversational-ai/client-toolkit/ios#destroy)
* Toolkit
* [`IConversationalAIAPIEventHandlers interface`](/en/api-reference/api-ref/conversational-ai/client-toolkit/web#iconversationalaiapieventhandlers-interface)
* [`EConversationalAIAPIEvents`](/en/api-reference/api-ref/conversational-ai/client-toolkit/web#econversationalaiapievents)
* [`subscribeMessage`](/en/api-reference/api-ref/conversational-ai/client-toolkit/web#subscribemessage)
* [`unsubscribeMessage`](/en/api-reference/api-ref/conversational-ai/client-toolkit/web#unsubscribe)
* [`destroy`](/en/api-reference/api-ref/conversational-ai/client-toolkit/web#destroy)
# Get agent state (/en/ai/best-practices/get-agent-state)
If you need to track agent state changes in your client app, you can use either Signaling (RTM) Message or RTM Presence. This guide explains how to get agent state using RTM Message and describes [how the two approaches differ](#rtm-message-vs-rtm-presence).
Info
* To monitor state changes using client components, see [Listen to agent events](/en/ai/build/handle-runtime-events/webhooks).
* To get agent state through RTM Presence, continue using your existing Presence implementation.
## Understand the tech [#understand-the-tech]
The Conversational AI Engine pushes agent state change events to the client as individual RTM messages through the `onMessageEvent` callback. Unlike RTM Presence, which synchronizes state as key-value pairs, RTM Message wraps each state change in a separate event message. This makes it easier to record state change timestamps, process events sequentially, or consume state events together with other RTM Message events.
The inner `message` field of each RTM message is a JSON string that requires a second parse to access the state event data. Use the `event_type` field in the inner message to identify the event type.
The following agent states are pushed through RTM Message:
* `state.listening`
* `state.thinking`
* `state.speaking`
The flow works as follows:
```text
Agent state change
├─ listening -> true / false
├─ thinking -> true / false
└─ speaking -> true / false
↓
RTM Message
↓
Client parses inner payload and identifies event by event_type
```
### State event types [#state-event-types]
The Conversational AI Engine sends an RTM Message each time the agent state changes. Each message corresponds to a single state change event.
| `event_type` | Description |
| :---------------- | :-------------------------------------------------- |
| `state.listening` | The agent starts or stops listening for user input. |
| `state.thinking` | The agent starts or stops processing user input. |
| `state.speaking` | The agent starts or stops speaking. |
### Message structure [#message-structure]
The inner `message` field is a JSON string. After deserialization, the structure is as follows:
```json
{
"event_id": "xxxx",
"event_type": "state.listening",
"event_ms": 1611566412672,
"payload": {
"value": true,
"timestamp": 1611566412600
}
}
```
| Field | Type | Description |
| :------------------ | :------ | :-------------------------------------------------------------------------------------- |
| `event_id` | string | Unique event ID. |
| `event_type` | string | State event type. One of `state.listening`, `state.thinking`, or `state.speaking`. |
| `event_ms` | integer | Timestamp when the event was sent, in milliseconds. |
| `payload.value` | boolean | Current state value. `true` means the agent entered the state; `false` means it exited. |
| `payload.timestamp` | integer | Timestamp when the state change actually occurred, in milliseconds. |
## Prerequisites [#prerequisites]
Before you begin, ensure that you have:
* Completed the basic agent integration. See [Quickstart](/en/ai/get-started/quickstart).
* Integrated the RTM SDK and implemented message listener logic. See [RTM quickstart](https://docs.agora.io/en/signaling/get-started/sdk-quickstart).
* Enabled the RTM service and set `data_channel` to `rtm` when starting the agent.
## Implementation [#implementation]
### Enable RTM when starting the agent [#enable-rtm-when-starting-the-agent]
When calling [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join), include the following configuration:
```json
{
"properties": {
"advanced_features": {
"enable_rtm": true
},
"parameters": {
"data_channel": "rtm"
}
}
}
```
### Listen for and parse state messages [#listen-for-and-parse-state-messages]
After logging in to RTM and subscribing to the channel with the same name as the agent, parse state messages in the RTM message callback. The handling logic consists of the following steps:
1. Deserialize the RTM `message` field into a state event object.
2. Check that the deserialized result contains `event_type` and `payload`.
3. Dispatch handling based on `event_type`, and use `payload.value` to update your app state.
In the following examples:
* The inner `message` field is a JSON string and must be deserialized again to obtain `event_type` and `payload`.
* `event_type` identifies the specific state type.
* `payload.value` indicates whether the agent is entering (`true`) or exiting (`false`) the state.
* `payload.timestamp` can be used for precise ordering or logging.
Android
iOS
Web
```kotlin
val rtmConfig = RtmConfig()
rtmConfig.eventListener = object : RtmEventListener {
override fun onMessageEvent(event: MessageEvent) {
// 1. Deserialize the inner message (JSON string)
val payload = JSONObject(event.message.data)
if (!payload.has("event_type") || !payload.has("payload")) {
return
}
val eventType = payload.getString("event_type")
val statePayload = payload.getJSONObject("payload")
val value = statePayload.getBoolean("value")
val timestamp = statePayload.getLong("timestamp")
// 2. Dispatch based on event_type
when (eventType) {
"state.listening" -> handleListeningChanged(value, timestamp)
"state.thinking" -> handleThinkingChanged(value, timestamp)
"state.speaking" -> handleSpeakingChanged(value, timestamp)
}
}
}
mRtmClient = RtmClient.create(rtmConfig)
```
```swift
func rtmKit(_ rtmKit: AgoraRtmClientKit, didReceiveMessageEvent event: AgoraRtmMessageEvent) {
// 1. Deserialize the inner message (JSON string)
guard let jsonString = event.message.stringData,
let data = jsonString.data(using: .utf8),
let payload = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let eventType = payload["event_type"] as? String,
let statePayload = payload["payload"] as? [String: Any] else {
return
}
let value = statePayload["value"] as? Bool ?? false
let timestamp = statePayload["timestamp"] as? Int ?? 0
// 2. Dispatch based on event_type
switch eventType {
case "state.listening":
handleListeningChanged(value, timestamp)
case "state.thinking":
handleThinkingChanged(value, timestamp)
case "state.speaking":
handleSpeakingChanged(value, timestamp)
default:
break
}
}
```
```typescript
rtm.addEventListener("message", (event) => {
// 1. Deserialize the inner message (JSON string)
const payload = JSON.parse(event.message as string);
if (!payload.event_type || !payload.payload) {
return;
}
const { event_type, payload: statePayload } = payload;
// 2. Dispatch based on event_type
switch (event_type) {
case "state.listening":
handleListeningChanged(statePayload.value, statePayload.timestamp);
break;
case "state.thinking":
handleThinkingChanged(statePayload.value, statePayload.timestamp);
break;
case "state.speaking":
handleSpeakingChanged(statePayload.value, statePayload.timestamp);
break;
}
});
```
### Update app state [#update-app-state]
Use the received events to update your UI or internal state machine. For example:
* When `state.listening` with `payload.value = true` is received, display "Listening".
* When `state.thinking` with `payload.value = true` is received, display "Thinking".
* When `state.speaking` with `payload.value = true` is received, display "Speaking".
## RTM Message vs RTM Presence [#rtm-message-vs-rtm-presence]
The Conversational AI Engine supports getting agent state through both RTM Message and RTM Presence. The key differences are:
| | RTM Presence | RTM Message |
| :---------- | :------------------------------- | :------------------------------------------------------- |
| Return form | State key-value pairs | State event messages |
| Granularity | Better for reading current state | Better for handling each state change |
| Timing info | Relies on message arrival time | Includes explicit `event_ms` and `payload.timestamp` |
| Use case | Simple UI state display | Event-driven logic, logging, unified message consumption |
Choose based on your needs:
* If you only need to update the UI based on whether the agent is currently `listening`, `thinking`, or `speaking`, RTM Presence is simpler to integrate.
* If you want to process state changes together with captions, interruptions, or performance metrics in a unified RTM Message handler, use RTM Message.
* If you need to record the timestamp of each state change or analyze state transitions in sequence, use RTM Message.
## Considerations [#considerations]
Keep the following in mind when implementing this feature:
* **Double deserialization**: The inner `message` field is a JSON string and must be deserialized a second time to access `event_type` and `payload`.
* **Avoid duplicate UI updates**: If you are already driving UI state through RTM Presence, avoid having both implementations update the same UI state simultaneously, as this can cause redundant updates or state flickering.
* **`state.speaking` end timing**: A `state.speaking` value of `false` does not guarantee that the client has finished playing audio. The server estimates the speaking end time based on TTS audio length, so there may be a delay between the state switching to `false` and the client completing playback.
* **Scope of state messages**: State messages cover only `listening`, `thinking`, and `speaking` agent state events. Manual SoS and EoS signals and their callbacks are not included.
# Manually control start and end of speech (/en/ai/best-practices/manual-turn-control)
When interacting with a conversational AI agent, you may need to explicitly control when a user's speech starts and ends on the client side. This supports scenarios with strict turn-boundary requirements, such as AI interviews, interactive quizzes, or walkie-talkie style push-to-talk. This guide explains how to send manual Start of Speech (SoS) and End of Speech (EoS) requests directly through raw Signaling (RTM) messages, without integrating the Conversational AI client components, and how to handle the results returned by the server.
This approach is suitable when:
* You want to implement your own RTM send and receive logic instead of relying on client components.
* Your business already has an independent RTM message dispatch layer and needs to integrate Conversational AI events into that existing pipeline.
* You need to explicitly control when a user's speech starts and ends.
## Understand the tech [#understand-the-tech]
Manual turn control consists of two independent capabilities:
* **Manual SoS**: The client explicitly declares that the user has started speaking.
* **Manual EoS**: The client explicitly declares that the user has finished speaking.
You can enable either capability independently:
* Set only [`end_of_speech.mode`](/en/api-reference/api-ref/conversational-ai/join#properties-turn-detection-config-end-of-speech-mode) to `manual`: Suitable when start of speech is still determined by VAD or semantic detection, but end of speech is submitted explicitly by the user.
* Set both [`start_of_speech.mode`](/en/api-reference/api-ref/conversational-ai/join#properties-turn-detection-config-start-of-speech-mode) and `end_of_speech.mode` to `manual`: Suitable for a complete push-to-talk experience.
## Prerequisites [#prerequisites]
Before you begin, ensure that you have:
* Completed the basic steps for interacting with an agent. See [Quickstart](/en/ai/get-started/quickstart).
* Integrated the RTM SDK and implemented basic RTM login, subscription, and message listening logic. See [RTM quickstart](https://docs.agora.io/en/signaling/get-started/sdk-quickstart).
* Set `advanced_features.enable_rtm = true` when creating the agent.
## Implement manual control [#implement-manual-control]
### Enable manual turn control when starting the agent [#enable-manual-turn-control-when-starting-the-agent]
When calling [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join), first enable RTM, then decide whether to manually control SoS, EoS, or both, depending on your use case.
The following `curl` examples show two common configurations:
Manual EoS only
Manual SoS + EoS
```shell
curl --request POST \
--url https://api.agora.io/api/conversational-ai-agent/v2/projects//join \
--header 'Authorization: agora token="007abcxxxxxxx123"' \
--data '
{
"name": "manual-eos-agent",
"properties": {
"channel": "channel_name",
"token": "token",
"agent_rtc_uid": "0",
"remote_rtc_uids": [
"123"
],
"advanced_features": {
"enable_rtm": true
},
"asr": {
"language": "en-US"
},
"llm": {
"url": "https://api.xxxx/v1/xxxx",
"api_key": "xxx",
"system_messages": [
{
"role": "system",
"content": "You are a helpful chatbot."
}
],
"greeting_message": "Hello, how can I help you?",
"failure_message": "Sorry, I am unable to answer that question.",
"max_history": 10,
"params": {
"model": "xxxx"
}
},
"tts": {
"vendor": "minimax",
"params": {
"key": "your-minimax-key",
"model": "speech-01-turbo",
"voice_setting": {
"voice_id": "female-shaonv",
"speed": 1,
"vol": 1,
"pitch": 0,
"emotion": "happy"
},
"audio_setting": {
"sample_rate": 16000
}
}
},
"turn_detection": {
"mode": "default",
"config": {
"start_of_speech": {
"mode": "vad"
},
"end_of_speech": {
"mode": "manual"
}
}
}
}
}
'
```
```shell
curl --request POST \
--url https://api.agora.io/api/conversational-ai-agent/v2/projects//join \
--header 'Authorization: agora token="007abcxxxxxxx123"' \
--data '
{
"name": "manual-sos-eos-agent",
"properties": {
"channel": "channel_name",
"token": "token",
"agent_rtc_uid": "0",
"remote_rtc_uids": [
"123"
],
"advanced_features": {
"enable_rtm": true
},
"asr": {
"language": "en-US"
},
"llm": {
"url": "https://api.xxxx/v1/xxxx",
"api_key": "xxx",
"system_messages": [
{
"role": "system",
"content": "You are a helpful chatbot."
}
],
"greeting_message": "Hello, how can I help you?",
"failure_message": "Sorry, I am unable to answer that question.",
"max_history": 10,
"params": {
"model": "xxxx"
}
},
"tts": {
"vendor": "minimax",
"params": {
"key": "your-minimax-key",
"model": "speech-01-turbo",
"voice_setting": {
"voice_id": "female-shaonv",
"speed": 1,
"vol": 1,
"pitch": 0,
"emotion": "happy"
},
"audio_setting": {
"sample_rate": 16000
}
}
},
"turn_detection": {
"mode": "default",
"config": {
"start_of_speech": {
"mode": "manual"
},
"end_of_speech": {
"mode": "manual"
}
}
}
}
}
'
```
After you start the agent, save the following information for use in later steps:
* `channelName`: The name of the channel used for this session.
* `userId`: The RTM login ID of the current user.
* `agentUserId`: The RTM user ID of the agent, which typically matches `agent_rtc_uid`.
### Log in to RTM and subscribe to channel messages [#log-in-to-rtm-and-subscribe-to-channel-messages]
Before sending manual SoS/EoS requests, the client must complete RTM login and subscribe to `channelName`. This ensures you receive the agent's processing results promptly.
Android
```kotlin
val rtmConfig = RtmConfig.Builder(appId, userId.toString()).build()
val rtmClient = RtmClient.create(rtmConfig)
rtmClient.addEventListener(object : RtmEventListener {
override fun onMessageEvent(event: MessageEvent?) {
event ?: return
val message = event.message ?: return
val rawJson = when (message.type) {
RtmConstants.RtmMessageType.BINARY -> {
val bytes = message.data as? ByteArray ?: return
String(bytes, Charsets.UTF_8)
}
else -> message.data as? String ?: return
}
handleAgentRtmMessage(
publisherId = event.publisherId.orEmpty(),
channelName = event.channelName.orEmpty(),
customType = event.customType.orEmpty(),
rawJson = rawJson
)
}
})
rtmClient.login(rtmToken, object : ResultCallback {
override fun onSuccess(responseInfo: Void?) {
val options = SubscribeOptions().apply {
withMessage = true
withPresence = true
}
rtmClient.subscribe(channelName, options, object : ResultCallback {
override fun onSuccess(responseInfo: Void?) {
// RTM subscription succeeded.
}
override fun onFailure(errorInfo: ErrorInfo) {
// Handle subscription failure.
}
})
}
override fun onFailure(errorInfo: ErrorInfo) {
// Handle RTM login failure.
}
})
```
Info
Login to RTM and subscribe to the channel before starting the agent to avoid missing callback messages sent during the agent's startup phase.
### Send manual SoS when the user starts speaking [#send-manual-sos-when-the-user-starts-speaking]
When your business logic determines that the user has started speaking, send an RTM USER peer-to-peer message to `agentUserId`. Use `RtmConstants.RtmChannelType.USER` as the channel type, and set `customType` to `user.manual_sos`.
Android
```kotlin
fun publishManualSos(
rtmClient: RtmClient,
agentUserId: String,
completion: (String, ErrorInfo?) -> Unit
) {
val requestId = "sos-req-${System.currentTimeMillis()}-${UUID.randomUUID()}"
val body = JSONObject(mapOf("request_id" to requestId)).toString()
val options = PublishOptions().apply {
setChannelType(RtmConstants.RtmChannelType.USER)
setCustomType("user.manual_sos")
}
rtmClient.publish(agentUserId, body, options, object : ResultCallback {
override fun onSuccess(responseInfo: Void?) {
completion(requestId, null)
}
override fun onFailure(errorInfo: ErrorInfo) {
completion(requestId, errorInfo)
}
})
}
```
The `publish` call sends the following RTM message to the agent.
```json
{
"customType": "user.manual_sos",
"publisher": "123",
"message": "{\"request_id\":\"sos-req-20260612-001\"}"
}
```
Info
* A successful RTM `publish` call only confirms that the message was sent.
* Whether the agent actually accepted the SoS request is confirmed by the `user.manual_sos.result` callback.
### Send manual EoS when the user finishes speaking [#send-manual-eos-when-the-user-finishes-speaking]
When your business logic determines that the user has finished speaking, send an RTM USER peer-to-peer message to `agentUserId`. Use `RtmConstants.RtmChannelType.USER` as the channel type, and set `customType` to `user.manual_eos`.
Android
```kotlin
fun publishManualEos(
rtmClient: RtmClient,
agentUserId: String,
completion: (String, ErrorInfo?) -> Unit
) {
val requestId = "eos-req-${System.currentTimeMillis()}-${UUID.randomUUID()}"
val body = JSONObject(mapOf("request_id" to requestId)).toString()
val options = PublishOptions().apply {
setChannelType(RtmConstants.RtmChannelType.USER)
setCustomType("user.manual_eos")
}
rtmClient.publish(agentUserId, body, options, object : ResultCallback {
override fun onSuccess(responseInfo: Void?) {
completion(requestId, null)
}
override fun onFailure(errorInfo: ErrorInfo) {
completion(requestId, errorInfo)
}
})
}
```
The `publish` call sends the following RTM message to the agent.
```json
{
"customType": "user.manual_eos",
"publisher": "123",
"message": "{\"request_id\":\"eos-req-20260612-001\"}"
}
```
Info
* A successful RTM `publish` call only confirms that the message was sent.
* Whether the agent actually accepted the EoS request is confirmed by the `user.manual_eos.result` callback.
### Listen for and handle callback messages [#listen-for-and-handle-callback-messages]
The results of manual SoS/EoS requests are returned as RTM channel messages. After receiving a message, parse the message body as JSON, then use the `event_type` field in the inner `message` to distinguish the event type.
#### Identify callback message types [#identify-callback-message-types]
Common callback types include:
* `user.manual_sos.result`: The server's processing result for a client manual SoS request.
* `user.manual_eos.result`: The server's processing result for a client manual EoS request.
* `assistant.manual_eos.result`: In manual mode, the server automatically triggers EoS due to a protective policy, such as a maximum speaking duration limit.
user.manual_sos.result
user.manual_eos.result
assistant.manual_eos.result
```json
{
"event_type": "user.manual_sos.result",
"event_id": "evt_xxx",
"event_ms": 1773901235435,
"payload": {
"success": true,
"request_id": "sos-req-1773901235000-a1b2c3d4",
"turn_id": 12
}
}
```
```json
{
"event_type": "user.manual_eos.result",
"event_id": "evt_xxx",
"event_ms": 1773901240000,
"payload": {
"success": true,
"request_id": "eos-req-1773901240000-b2c3d4e5",
"turn_id": 12
}
}
```
```json
{
"event_type": "assistant.manual_eos.result",
"event_id": "evt_xxx",
"event_ms": 1773901250000,
"payload": {
"reason": "max_duration_reached",
"max_duration_ms": 60000,
"turn_id": 12
}
}
```
#### Handle result callbacks using `request_id` [#handle-result-callbacks-using-request_id]
In your RTM message callback, Agora recommends consistently parsing the `event_type`, `request_id`, `success`, and `turn_id` fields, and updating your app state based on the result.
Android
```kotlin
data class ManualResult(
val type: String,
val requestId: String,
val success: Boolean,
val turnId: Long?,
val errorMessage: String?
)
fun handleAgentRtmMessage(
publisherId: String,
channelName: String,
customType: String,
rawJson: String
) {
val json = JSONObject(rawJson)
val type = json.optString("event_type").ifEmpty {
json.optString("object")
}
val payload = json.optJSONObject("payload")
when (type) {
"user.manual_sos.result",
"user.manual_eos.result" -> {
if (payload == null) return
val result = ManualResult(
type = type,
requestId = payload.optString("request_id"),
success = payload.optBoolean("success", false),
turnId = payload.optLongOrNull("turn_id"),
errorMessage = payload.optString("error_message").ifEmpty { null }
)
onUserManualResult(publisherId, result)
}
"assistant.manual_eos.result" -> {
if (payload == null) return
val reason = payload.optString("reason")
val maxDurationMs = payload.optLong("max_duration_ms")
val turnId = payload.optLong("turn_id")
onAgentManualEos(
agentUserId = publisherId,
reason = reason,
maxDurationMs = maxDurationMs,
turnId = turnId
)
}
}
}
fun JSONObject.optLongOrNull(name: String): Long? {
return if (has(name) && !isNull(name)) optLong(name) else null
}
```
Pay particular attention to the following when implementing this logic:
* Use `request_id` to correlate the result with the SoS/EoS request you previously sent.
* Use `publisherId` to identify the agent's RTM user ID.
* Use the `event_type` field in the message body to distinguish event types. Do not rely on the outer RTM message's `customType`.
## See also [#see-also]
* [Get agent state](/en/ai/best-practices/get-agent-state)
* [Interrupt agent](/en/ai/build/shape-the-conversation/interrupt-agent)
* [Receive webhook notifications](/en/ai/build/handle-runtime-events/webhooks)
* [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join)
# Optimize latency (/en/ai/best-practices/optimize-latency)
Latency is a key factor affecting user experience in conversational AI agent scenarios. This guide helps you understand and optimize conversation latency to improve user experience.
## How latency works [#how-latency-works]
Understanding latency composition helps you identify optimization opportunities.
### Cascaded architecture latency [#cascaded-architecture-latency]
When using the cascaded architecture with ASR, LLM, and TTS components, end-to-end latency consists of the following:
```text
End-to-end latency = RTC latency
+ Algorithm preprocessing latency
+ ASR latency
+ LLM latency
+ TTS latency
(+ Avatar latency)
```
### Latency for each component [#latency-for-each-component]
The following table shows typical latency ranges for each component based on actual test data:
| Component | Latency metric | Description | Typical latency range (ms) |
| ----------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- |
| RTC | Audio and video latency | Includes audio capture, encoding, network transmission, decoding, and playback | 150-300 |
| Algorithm preprocessing | Preprocessing latency | Includes VAD (Voice Activity Detection), intelligent interruption handling (AIVAD), and other algorithm processing time | 720-940\* |
| ASR | `asr_ttlw` | Time To Last Word. The latency from when the user stops speaking to when ASR outputs the last word. | 400-700 |
| LLM | `llm_ttfb` / `llm_ttfs` | TTFB: Time To First Byte, the first byte latency. TTFS: Time To First Sentence, the first sentence latency. | 250-1000 |
| TTS | `tts_ttfb` | Time To First Byte. The response latency from when the TTS request starts to when the first byte is received. | 100-350 |
| Avatar rendering | Rendering latency | The latency from when the avatar module receives the first frame of TTS audio to when it generates and synchronizes the first frame of audio and video (if enabled) | 50-200 |
\* Algorithm preprocessing latency is based on `silence_duration_ms` set to 640 ms. Adjusting this parameter affects preprocessing latency.
Info
Test data shows that LLM typically contributes the most to overall latency. Optimizing LLM selection and configuration is key to reducing end-to-end latency.
### Real-world latency example [#real-world-latency-example]
The following example shows latency data from three conversation turns in an actual conversation. This data comes from the [`111 agent metrics`](../reference/event-types#111-agent-metrics) event in Agora's [message notification service](../build/handle-runtime-events/webhooks):
```json
{
"metrics": [
{
"turn_id": 1,
"tts_ttfb": 61
},
{
"turn_id": 2,
"asr_ttlw": 141,
"llm_ttfb": 270,
"llm_ttfs": 482,
"tts_ttfb": 90
},
{
"turn_id": 3,
"asr_ttlw": 103,
"llm_ttfb": 306,
"llm_ttfs": 948,
"tts_ttfb": 106
}
]
}
```
{/* ### MLLM architecture latency
When using MLLM (Multimodal Large Language Model), the model processes audio input directly and generates audio output without separate ASR, LLM, and TTS components. This results in simpler latency composition:
```text
End-to-end latency = RTC latency
+ Algorithm preprocessing latency
+ MLLM latency
```
You can obtain the `mllm_ttfb` metric by listening to the `111 agent metrics` event. This metric represents the response latency from when the MLLM request starts to when the first byte is received.
:::info[Info]
* MLLM architecture typically achieves lower end-to-end latency by reducing data conversion and transmission between components.
* When MLLM is enabled, the graceful interruption (AIVAD) feature is disabled, which reduces algorithm preprocessing latency.
:::
*/}
## Monitor latency metrics [#monitor-latency-metrics]
The Conversational AI engine provides two ways to monitor latency metrics for each conversation turn:
### Use client components [#use-client-components]
If you use client components (Android, iOS, or Web), you can listen to agent performance metrics in real time by registering the `onAgentMetrics` callback.
Android
iOS
Web
```kotlin
api.addHandler(object : IConversationalAIAPIEventHandler {
override fun onAgentMetrics(agentUserId: String, metric: Metric) {
when (metric.type) {
ModuleType.ASR -> {
Log.d("Metrics", "ASR TTLW: \${metric.value}ms")
}
ModuleType.LLM -> {
// metric.name can be "ttfb" or "ttfs"
Log.d("Metrics", "LLM \${metric.name}: \${metric.value}ms")
}
ModuleType.TTS -> {
Log.d("Metrics", "TTS TTFB: \${metric.value}ms")
}
ModuleType.TOTAL -> {
Log.d("Metrics", "Total Delay: \${metric.value}ms")
}
else -> {
Log.d("Metrics", "\${metric.type}: \${metric.name} = \${metric.value}ms")
}
}
}
})
```
```swift
func onAgentMetrics(agentUserId: String, metrics: Metric) {
switch metrics.type {
case .asr:
print("ASR TTLW: \(metrics.value)ms")
case .llm:
print("LLM \(metrics.name): \(metrics.value)ms")
case .tts:
print("TTS TTFB: \(metrics.value)ms")
case .total:
print("Total Delay: \(metrics.value)ms")
case .unknown:
print("Unknown metric: \(metrics.name) = \(metrics.value)ms")
}
}
```
```ts
conversationalAIAPI.on(
EConversationalAIAPIEvents.AGENT_METRICS,
(agentUserId: string, metrics: Metric) => {
console.log(`[\${metrics.type}] \${metrics.name}: \${metrics.value}ms`);
if (metrics.type === 'TOTAL') {
console.log(`Total delay for turn: \${metrics.value}ms`);
}
}
);
```
For detailed integration steps and API reference, see [Webhooks](../build/handle-runtime-events/webhooks).
### Use Message Notification Service [#use-message-notification-service]
If you have enabled Agora's Message Notification Service, you can obtain agent performance metrics by receiving the `agent metrics` event where `eventType` is `111`.
**Event callback example**
```json
{
"noticeId": "2000001428:4330:107",
"productId": 17,
"eventType": 111,
"notifyMs": 1611566412672,
"payload": {
"agent_id": "A42AC47Hxxxxxxxx4PK27ND25E",
"start_ts": 1000,
"stop_ts": 1672531200,
"channel": "test-channel",
"metrics": [
{
"turn_id": 1,
"tts_ttfb": 61
},
{
"turn_id": 2,
"asr_ttlw": 141,
"llm_ttfb": 270,
"llm_ttfs": 482,
"tts_ttfb": 90,
}
]
}
}
```
For detailed event field descriptions, see [Notification event 111 agent metrics](../reference/event-types#111-agent-metrics).
## Optimize cascaded architecture latency [#optimize-cascaded-architecture-latency]
To reduce latency in cascaded architecture, focus on optimizing individual components, geographic deployment, and RTC settings.
### Optimize LLM, ASR, and TTS components [#optimize-llm-asr-and-tts-components]
LLM is typically the component that contributes the most to latency. Optimizing LLM can significantly reduce overall latency.
**Choose low-latency vendors**
LLM, ASR, and TTS vendors and models vary significantly in response speed. Refer to the [Conversational AI Performance Lab](https://www.agora.io/en/conversational-ai-performance-lab) to compare performance metrics across vendors.
**Optimize parameter configuration**
When creating an agent, read the vendor documentation for ASR, LLM, and TTS to understand available parameters and tune them for your use case. The following are general optimization approaches:
* **LLM**
* **Choose smaller models**: Models such as `gpt-4o-mini` and `gemini-2.5-flash` typically respond faster than larger models.
* **Limit `max_tokens`**: Reducing the maximum number of tokens generated can lower TTFS (first sentence latency).
* **Enable streaming response**: Ensure `stream: true` so the agent can start speaking as soon as possible.
* **ASR**
* **Use vendor-recommended sampling rate**: Use the sampling rate recommended by the vendor, such as 16 kHz, to avoid unnecessary resampling.
* **Limit language model**: Use the `phrases` or `context` parameter to provide domain-specific vocabulary and improve recognition speed.
* **Disable non-essential features**: Some ASR vendors provide advanced parameters such as punctuation and tone output. You can disable these based on your use case to improve response speed.
* **TTS**
* **Choose faster modes**: Some TTS providers offer modes such as turbo or low-latency, which typically respond faster than default mode.
* **Choose simpler voices**: Some TTS providers offer voices with varying complexity. Choosing less complex voices can reduce generation time.
* **Disable non-essential features**: Some TTS vendors provide advanced parameters such as profanity\_filter, punctuation\_filter, and diarization. You can disable these based on your use case to improve response speed.
The following example shows how to optimize LLM parameters:
```json
{
"properties": {
"llm": {
"url": "https://api.openai.com/v1/chat/completions",
"api_key": "your_api_key",
"params": {
"model": "gpt-4o-mini", // Select the faster-responding model
"temperature": 0.7,
"max_tokens": 150, // Limit the generation length to reduce latency
"stream": true // Enable streaming response
}
}
}
}
```
{/* ### Optimize geographic deployment
**Understand the default location selection strategy**
When regional access restrictions are not enabled or when using the `GLOBAL` region, the Conversational AI engine automatically selects the nearest server to deploy the agent based on the IP address of the configured LLM URL. The system includes the following capabilities:
1. **Smart deployment**: The system deploys the Conversational AI engine service in the region that corresponds to the IP address of the LLM URL.
2. **Proximity selection**: If no servers are available in the corresponding region, the system selects the nearest available region.
3. **Failover**: When the service in a region becomes unavailable, the system switches to another available region.
**Optimize deployment region**
To minimize network latency, Agora recommends:
1. **Deploy ASR, LLM, and TTS in the same region**: Choose vendors that support the same region to reduce cross-region network latency.
2. **Use regional access restrictions**: If your users are concentrated in a specific region, use the `geofence` parameter to restrict the Conversational AI engine to servers in that region:
```json
{
"properties": {
"geofence": {
"area": "NORTH_AMERICA" // Or EUROPE, ASIA, etc.
},
"llm": {
"url": "https://api.openai.com/v1/chat/completions" // Use LLM endpoint in North America region
},
"tts": {
"url": "wss://api.elevenlabs.io/v1/text-to-speech/{voice_id}/stream-input" // Use TTS endpoint in North America region
}
}
}
```
For detailed regional access restriction configuration, see [Restrict agent zones](regional-restrictions). */}
### Optimize RTC latency [#optimize-rtc-latency]
RTC latency includes audio capture, encoding, network transmission, decoding, and playback. To optimize RTC latency, configure audio settings on the client side.
**Use AI conversation scenario**
Agora RTC SDK 4.5.1 and later supports the AI conversation scenario (`AUDIO_SCENARIO_AI_CLIENT`), which is specifically optimized for AI conversations and includes:
* Optimized audio 3A algorithms (echo cancellation, noise reduction, and gain control)
* Lower audio capture and playback latency
* Audio processing tailored to AI voice characteristics
Android
iOS
**Use the client-side component API (recommended)**
```kotlin
val config = ConversationalAIAPIConfig(
rtcEngine = rtcEngineInstance,
rtmClient = rtmClientInstance,
enableLog = true
)
val api = ConversationalAIAPIImpl(config)
// Load optimal audio settings
api.loadAudioSettings()
```
**Configure the RTC SDK directly**
```kotlin
val config = RtcEngineConfig()
config.mAudioScenario = Constants.AUDIO_SCENARIO_AI_CLIENT
rtcEngine = RtcEngine.create(config)
```
**Use the client-side component API (recommended)**
```swift
let config = ConversationalAIAPIConfig(
rtcEngine: rtcEngine,
rtmEngine: rtmEngine,
enableLog: true
)
convoAIAPI = ConversationalAIAPIImpl(config: config)
// Load optimal audio settings
convoAIAPI.loadAudioSettings()
```
**Configure the RTC SDK directly**
```swift
let config = AgoraRtcEngineConfig()
config.audioScenario = .aiClient
rtcEngine = AgoraRtcEngineKit.sharedEngine(with: config, delegate: delegate)
```
For detailed audio setting optimization, see [Optimize audio](audio-setup).
{/* ## Optimize MLLM architecture latency
If you use an MLLM (Multimodal Large Language Model) architecture, optimizing latency primarily relies on choosing low-latency MLLM vendors.
MLLM vendors vary in real-time performance. The Conversational AI engine currently supports:
* OpenAI Realtime API: Provides low-latency real-time audio processing capabilities
* Google Gemini Live: Supports multimodal real-time interaction
For detailed MLLM integration guides, see the MLLM provider pages under `AI > Models > MLLM`. */}
## Latency optimization checklist [#latency-optimization-checklist]
Use the following checklist to systematically optimize your conversational AI agent latency:
### Server-side optimization [#server-side-optimization]
* **Choose low-latency LLM models**: Refer to [Conversational AI Performance Lab](https://www.agora.io/en/conversational-ai-performance-lab) to select models with excellent TTFT and throughput performance.
* **Enable streaming response**: Ensure `stream: true`.
* **Choose low-latency ASR and TTS vendors**: Enable low-latency or turbo modes when available. Refer to [Conversational AI Performance Lab](https://www.agora.io/en/conversational-ai-performance-lab) to select models with low latency and high throughput performance.
* **Optimize geographic deployment**: Deploy ASR, LLM, and TTS in the same region.
### Client-side optimization [#client-side-optimization]
* **Use AI conversation scenario**: Set `AUDIO_SCENARIO_AI_CLIENT` (RTC SDK 4.5.1 and later).
* **Load optimal audio settings**: Call the `loadAudioSettings()` method of the client component.
* **Integrate required audio plugins**: Ensure integration of AI noise reduction and AI echo cancellation plugins.
* **Optimize network conditions**: Ensure stable user network connection and consider using SD-RTN™ to optimize network transmission.
### Monitoring and analysis [#monitoring-and-analysis]
* **Monitor latency metrics in real time**: Obtain latency data for each conversation turn through client components or NCS.
* **Identify latency bottlenecks**: Analyze which component contributes the most to latency.
* **Continuously optimize**: Adjust configuration based on actual data and conduct A/B testing.
## Balance latency and quality [#balance-latency-and-quality]
When optimizing latency, find a balance between response speed and conversation quality:
| Optimization strategy | Latency impact | Quality impact | Recommended scenario |
| ---------------------------- | ----------------------- | -------------------------- | ---------------------------------------------------------------- |
| Use smaller LLM models | ✅ Significantly reduces | ⚠️ May reduce | Latency-sensitive scenarios with relatively simple conversations |
| Limit `max_tokens` | ✅ Moderately reduces | ⚠️ May affect completeness | Scenarios requiring short responses |
| Regional access restrictions | ✅ Moderately reduces | No impact | Users concentrated in a specific region |
| Optimize RTC settings | ✅ Moderately reduces | No impact | All scenarios |
Tip
Monitor latency metrics to identify bottlenecks, then optimize the highest-contributing component rather than pursuing minimal latency across all components.
## References [#references]
Refer to the following resources for further details.
* [Webhooks](../build/handle-runtime-events/webhooks)
* [Optimize audio](audio-setup)
* [Notification event types](../reference/event-types)
* [Conversational AI Performance Lab](https://www.agora.io/en/conversational-ai-performance-lab)
{/* * MLLM provider pages under `AI > Models > MLLM` */}
# Record agent conversations (/en/ai/best-practices/record-agent-conversation)
This guide explains how to configure Agora [Cloud Recording](/en/api-reference/api-ref/cloud-recording) for Voice Agent conversations and avoid audio-scene conflicts when recording starts.
## Why this needs special handling [#why-this-needs-special-handling]
When you run a Voice Agent and Cloud Recording in the same channel, recording startup can briefly interrupt audio. The issue is usually caused by the agent audio scene falling back to a default mode when Cloud Recording joins the channel.
To keep audio stable, set the agent `audio_scenario` to `"chorus"` when you start the agent.
## Configure the agent [#configure-the-agent]
Set the agent's RTC audio scenario to `chorus` when you configure the agent:
Python SDK
TypeScript SDK
Go SDK
REST API
```python
from agora_agent import Agent
# client is your configured Agora client
agent = (
Agent(client)
.with_stt(...) # configure your STT vendor
.with_llm(...) # configure your LLM vendor
.with_tts(...) # configure your TTS vendor
.with_audio_scenario('chorus')
)
```
```typescript
import { Agent } from 'agora-agents';
// client is your configured Agora client
const agent = new Agent({ client })
.withStt(/* configure your STT vendor */)
.withLlm(/* configure your LLM vendor */)
.withTts(/* configure your TTS vendor */)
.withAudioScenario('chorus');
```
```go
import "github.com/AgoraIO/agora-agents-go/v2/agentkit"
// client is your configured *agentkit.AgoraClient
agent := agentkit.NewAgent(client).
WithStt(/* configure your STT vendor */).
WithLlm(/* configure your LLM vendor */).
WithTts(/* configure your TTS vendor */).
WithAudioScenario(agentkit.AudioScenarioChorus)
```
Add or modify the `audio_scenario` field in the agent startup parameters:
```json
{
"properties": {
"parameters": {
"audio_scenario": "chorus"
}
}
}
```
## Complete example [#complete-example]
The following example shows an agent launch request with the recommended recording-safe configuration:
```json
{
"name": "TestConvoAgent",
"properties": {
"channel": "{{AccessChannel}}",
"token": "{{token}}",
"agent_rtc_uid": "0",
"remote_rtc_uids": [
"*"
],
"idle_timeout": 30,
"advanced_features": {
"enable_bhvs": true
},
"llm": {
"url": "https://api.openai.com/v1/chat/completions",
"api_key": "",
"system_messages": [
{
"role": "system",
"content": "You are a helpful chatbot."
}
],
"greeting_message": "Hello, how can I help you?",
"failure_message": "Sorry, I don't know how to answer this question.",
"max_history": 10,
"params": {
"model": "gpt-4o-mini"
}
},
"tts": {
"vendor": "microsoft",
"params": {
"key": "",
"region": "eastus",
"voice_name": "en-US-AndrewMultilingualNeural"
}
},
"turn_detection": {
"silence_duration_ms": 640
},
"parameters": {
"enable_dump": true,
"enable_error_message": true,
"audio_scenario": "chorus",
"enable_delay": true
}
}
}
```
## Start recording [#start-recording]
After the agent is running, call the [Start Cloud Recording API](/en/api-reference/api-ref/cloud-recording/start) to begin recording.
Example Cloud Recording request body:
```json
{
"cname": "{{AccessChannel}}",
"uid": "{{RecordingUID}}",
"clientRequest": {
"token": "{{token}}",
"recordingConfig": {
"maxIdleTime": 120,
"streamTypes": 0,
"audioProfile": 1,
"channelType": 1
},
"recordingFileConfig": {
"avFileType": [
"hls",
"mp4"
]
},
"storageConfig": {
"vendor": "{{Vendor}}",
"region": "{{Region}}",
"bucket": "{{Bucket}}",
"accessKey": "{{AccessKey}}",
"secretKey": "{{SecretKey}}"
}
}
}
```
## Related pages [#related-pages]
* [Start and stop an agent](../build/start-stop-agent)
* [Cloud Recording API reference](/en/api-reference/api-ref/cloud-recording)
# Restrict where agent can run (/en/ai/best-practices/regional-restrictions)
import { Parameter, ParameterList } from '@/components/mdx';
To comply with laws and regulations in different countries and regions, the Conversational AI Engine supports regional access restrictions. When you enable regional access restrictions, the Conversational AI Engine only accesses Agora servers in the designated region, regardless of the user's location.
For example, if you specify North America as the access region, two users who initiate calls from North America and Singapore have different experiences:
| Specified access region | User location | Actual access region | User experience |
|:-------|:-------|:-------|:-------|
| North America | North America | North America | Normal quality. |
| North America | Singapore | North America | Quality may be significantly impacted. Cross-regional public internet connections between the designated region and the user's location can result in poor network quality. If all servers in the specified region are unavailable, the service returns an error. |
## Understand the tech
When regional access restrictions are disabled, the Conversational AI Engine automatically selects the nearest server to deploy the agent based on the IP address of the LLM URL and supports failover:
1. **Intelligent deployment**: The system automatically deploys the Conversational AI Engine service in the region that corresponds to the IP address of the configured LLM URL.
1. **Nearest available region**: If no server is available in the corresponding region, the system automatically selects the nearest available region.
1. **Failover**: When a service becomes unavailable in a region, the system automatically switches to another available region.
:::caution[Caution]
When you enable regional access restrictions, the system strictly limits access to the specified region and does not perform cross-regional failover.
:::
## Implementation
### Configure regional access
To configure region access restrictions, set `properties.geofence` when you [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join).
The allowed region for server access.
The excluded region. Only available when `area` is set to `GLOBAL`.
### Configuration examples
Refer to the following examples to understand `geofence` configuration.
#### Restrict access to North America only
Python SDKTypeScript SDKGo SDKREST API
```python
from agora_agent import Agent
from agora_agent.agentkit import GeofenceConfig
# client is your configured Agora client
agent = (
Agent(client)
.with_llm(...) # configure your LLM vendor
.with_tts(...) # configure your TTS vendor
.with_geofence(GeofenceConfig(
area='NORTH_AMERICA',
))
)
```
```typescript
import { Agent } from 'agora-agents';
// client is your configured Agora client
const agent = new Agent({ client })
.withLlm(/* configure your LLM vendor */)
.withTts(/* configure your TTS vendor */)
.withGeofence({
area: 'NORTH_AMERICA',
});
```
```go
import "github.com/AgoraIO/agora-agents-go/v2/agentkit"
// client is your configured *agentkit.AgoraClient
agent := agentkit.NewAgent(client).
WithLlm(/* configure your LLM vendor */).
WithTts(/* configure your TTS vendor */).
WithGeofence(&agentkit.GeofenceConfig{
Area: agentkit.GeofenceAreaNorthAmerica,
})
```
Use the following request body when you [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join):
```json
{
"name": "customer_service",
"properties": {
"channel": "test_channel",
"token": "your_rtc_token",
"agent_rtc_uid": "123",
"remote_rtc_uids": ["456"],
"geofence": {
"area": "NORTH_AMERICA"
},
"llm": {
// ... LLM configuration
},
"tts": {
// ... TTS configuration
}
}
}
```
#### Allow global access but exclude India
Python SDKTypeScript SDKGo SDKREST API
```python
from agora_agent import Agent
from agora_agent.agentkit import GeofenceConfig
# client is your configured Agora client
agent = (
Agent(client)
.with_llm(...) # configure your LLM vendor
.with_tts(...) # configure your TTS vendor
.with_geofence(GeofenceConfig(
area='GLOBAL',
exclude_area='INDIA',
))
)
```
```typescript
import { Agent } from 'agora-agents';
// client is your configured Agora client
const agent = new Agent({ client })
.withLlm(/* configure your LLM vendor */)
.withTts(/* configure your TTS vendor */)
.withGeofence({
area: 'GLOBAL',
exclude_area: 'INDIA',
});
```
```go
import "github.com/AgoraIO/agora-agents-go/v2/agentkit"
// client is your configured *agentkit.AgoraClient
agent := agentkit.NewAgent(client).
WithLlm(/* configure your LLM vendor */).
WithTts(/* configure your TTS vendor */).
WithGeofence(&agentkit.GeofenceConfig{
Area: agentkit.GeofenceAreaGlobal,
ExcludeArea: agentkit.GeofenceExcludeAreaIndia.Ptr(),
})
```
Use the following request body when you [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join):
```json
{
"name": "customer_service",
"properties": {
"channel": "test_channel",
"token": "your_rtc_token",
"agent_rtc_uid": "123",
"remote_rtc_uids": ["456"],
"geofence": {
"area": "GLOBAL",
"exclude_area": "INDIA"
},
"llm": {
// ... LLM configuration
},
"tts": {
// ... TTS configuration
}
}
}
```
### Configure RTC regional access
The Conversational AI Engine and Agora RTC services are independent, and their regional access restriction configurations are separate. To implement complete end-to-end regional access restrictions and ensure that the entire call chain with the agent is confined to a specified region, you must also configure RTC regional access restrictions. For more information, see [Restrict media zones](../../realtime-media/video/build/manage-connection-and-quality/geofencing).
## Data residency
In addition to configuring regional access restrictions for the Conversational AI Engine, some LLM, TTS, and ASR vendors offer data residency services in different regions. You can ensure that data is not transferred across regions by configuring the URLs used by the LLM, TTS, and ASR modules.
For example, ElevenLabs supports selecting data processing regions through different URL endpoints. For more information, see the [ElevenLabs documentation](https://elevenlabs.io/docs/overview/administration/data-residency).
The following example shows how to configure the ElevenLabs TTS service using a URL endpoint for the European region:
```json
{
"properties": {
"tts": {
"vendor": {
"name": "elevenlabs"
},
"url": "wss://api.eu.elevenlabs.io/v1/text-to-speech/{voice_id}/stream-input"
}
}
}
```
## Best practices
To ensure that data remains entirely within the designated region, follow these recommendations:
1. **Configure regional restrictions for the Conversational AI Engine**: Use the `geofence` object to restrict server access regions.
2. **Configure RTC regional restrictions**: Configure the corresponding regional access restrictions in the RTC SDK.
3. **Select regional AI services**: When configuring LLM, TTS, and ASR services, select a provider that supports regional data residency and use the corresponding regional URL endpoint.
4. **Verify the configuration**: Before deploying to production, thoroughly test that regional access restrictions work correctly and that data is not transferred across regions.
## Considerations
Keep the following in mind when implementing regional access restrictions:
- **Performance impact**: Regional access restrictions may affect user experience. If a user's location is far from the designated region, network latency may increase.
- **Service availability**: When regional access restrictions are enabled and the server in the specified region is unavailable or lacks sufficient resources, the service returns an error and does not automatically switch to another region.
- **Configuration consistency**: Ensure that the regional configurations of the Conversational AI Engine, RTC SDK, and LLM, TTS, and ASR services are consistent to prevent data transfer across regions.
- **Compliance requirements**: Before configuring regional access restrictions, understand the legal and regulatory requirements of the target region to ensure that the configuration complies with local data protection and privacy regulations.
# Architecture (/en/ai/build/architecture)
The TEN framework uses a graph structure of interconnected nodes to define data flow between extensions. Subgraphs extend this architecture by providing a powerful reuse mechanism that allows you to break down complex graph structures into modular, reusable components.
Subgraphs improve code organization and maintainability by enabling you to create self-contained graph modules that can be shared across different applications or used multiple times within the same application. This modular approach simplifies the development of complex systems while maintaining the same runtime performance characteristics as regular graphs.
## Understand the tech [#understand-the-tech]
Subgraphs preserve the fundamental principle of TEN graphs: defining how data flows between extensions. Rather than introducing new runtime complexity, subgraphs act as a development-time abstraction that gets flattened into the larger graph structure during deployment.
This design approach provides the organizational benefits of modularity while using the same graph execution mechanism that powers all TEN applications.
### Design principles [#design-principles]
Subgraphs are built on the following foundational principles that ensure they integrate seamlessly with the TEN framework while providing meaningful development benefits:
* **Independence**: Every subgraph is a complete, self-contained graph that can run independently or be embedded as a component within larger graphs. This independence means you can develop, test, and validate subgraphs in isolation before integrating them into complex applications.
* **Tool-friendly design**: Subgraphs provide structured metadata through `exposed_messages` and `exposed_properties` that development tools can use to offer intelligent suggestions, validation, and debugging capabilities. This enhanced tooling support improves the development experience without adding runtime overhead.
* **Flattening mechanism**: At deployment time, subgraphs are flattened into standard graph structures, ensuring that runtime performance remains identical to regular graphs. This approach provides development-time modularity benefits while maintaining the proven performance characteristics of the core TEN graph engine.
* **Simplicity**: Subgraph design prioritizes simplicity. Rather than providing patching mechanisms or complex configuration options at reference points, subgraphs use straightforward mapping and exposure patterns. If a subgraph needs modification, you modify the original definition directly, avoiding complexity cascades and maintenance overhead.
This design approach allows you to treat subgraphs as black boxes. You can use them without understanding their internal complexity, while the framework handles the technical details of integration and execution.
### Key features [#key-features]
Subgraphs provide four essential features that enable modular graph development and seamless integration within TEN applications:
* **Message exposure**: Subgraphs declare their external interfaces through the `exposed_messages` field, specifying which message types (commands, data, audio, video) can be sent to or received from the subgraph. This exposure mechanism allows development tools to provide intelligent autocomplete and validation while hiding internal implementation details from external consumers.
* **Property exposure**: Through the `exposed_properties` field, subgraphs can selectively expose internal extension properties to external configuration. This controlled exposure enables customization of subgraph behavior without revealing the entire internal structure, maintaining encapsulation while providing necessary flexibility.
* **Namespace management**: Each referenced subgraph creates its own namespace, preventing naming conflicts between elements in different subgraphs. When you reference a subgraph with name `subgraph_1`, all internal elements become accessible through the namespace syntax (e.g., `subgraph_1:ext_name`), ensuring global uniqueness across complex graph hierarchies.
* **Cross-graph connections**: Subgraphs enable sophisticated connection patterns that span multiple graph boundaries. You can connect directly to subgraph interfaces, connect to specific elements within subgraphs, or even establish connections between elements in different subgraphs. This flexibility supports complex data flow architectures while maintaining clear organizational boundaries.
These features work together to provide a powerful abstraction layer that simplifies complex graph development without sacrificing the flexibility and performance that TEN applications require.
## Creating subgraphs [#creating-subgraphs]
### Subgraph definition [#subgraph-definition]
A subgraph is defined in a standard JSON file that contains the same basic structure as any TEN graph, with additional metadata for external interfaces. Here's a basic example:
```json
{
"nodes": [
{
"type": "extension",
"name": "audio_processor",
"addon": "audio_enhancement"
},
{
"type": "extension",
"name": "audio_filter",
"addon": "noise_reduction"
}
],
"connections": [
{
"extension": "audio_processor",
"audio_frame": [
{
"name": "processed_audio",
"dest": [
{
"extension": "audio_filter"
}
]
}
]
}
],
"exposed_messages": [
{
"extension": "audio_processor",
"type": "audio_frame_in",
"name": "raw_audio"
},
{
"extension": "audio_filter",
"type": "audio_frame_out",
"name": "clean_audio"
}
],
"exposed_properties": [
{
"extension": "audio_processor",
"name": "sample_rate",
"alias": "input_sample_rate"
},
{
"extension": "audio_filter",
"name": "noise_threshold",
"alias": "filter_sensitivity"
}
]
}
```
Following is the structure breakdown:
* **Nodes section**: Defines the extensions that make up the subgraph, just like in a regular graph. Each extension specifies its type, name, and addon identifier.
* **Connections section**: Establishes data flow between extensions within the subgraph. These internal connections define how the subgraph processes data internally.
* **Exposed messages section**: Declares which message interfaces are available for external connections. This acts as the subgraph's public API, allowing other graphs to send data to or receive data from the subgraph without knowing its internal structure.
* **Exposed properties section**: Specifies which extension properties can be configured externally. The `alias` field provides user-friendly names for external configuration, abstracting away internal implementation details.
This sample subgraph can function as a standalone audio processing pipeline or be embedded within larger applications that need audio enhancement capabilities.
### Exposed interfaces [#exposed-interfaces]
Subgraphs use two interface types to control what external graphs can access and configure. These interfaces act as the subgraph's public API, providing controlled access while maintaining internal encapsulation.
#### exposed\_messages [#exposed_messages]
The `exposed_messages` field defines which message interfaces are available for external connections. This allows other graphs to interact with your subgraph without needing to understand its internal structure.
**Message exposure structure:**
```json
{
"exposed_messages": [
{
"extension": "internal_extension_name",
"type": "message_direction_and_type",
"name": "message_name"
}
]
}
```
**Message types:**
* `cmd_in` / `cmd_out`: Command messages for control and responses
* `data_in` / `data_out`: General data messages
* `audio_frame_in` / `audio_frame_out`: Audio stream data
* `video_frame_in` / `video_frame_out`: Video stream data
**Example:**
```json
{
"exposed_messages": [
{
"extension": "speech_recognizer",
"type": "audio_frame_in",
"name": "audio_input"
},
{
"extension": "text_processor",
"type": "data_out",
"name": "transcribed_text"
},
{
"extension": "speech_recognizer",
"type": "cmd_in",
"name": "configure_language"
}
]
}
```
This example exposes an audio input, text output, and configuration command, allowing external graphs to send audio data and receive transcribed text while configuring the recognition language.
#### exposed\_properties [#exposed_properties]
The `exposed_properties` field specifies which internal extension properties can be configured from outside the subgraph. This enables customization without exposing the entire internal configuration.
**Property exposure structure:**
```json
{
"exposed_properties": [
{
"extension": "internal_extension_name",
"name": "internal_property_name",
"alias": "external_property_name"
}
]
}
```
* `extension`: The internal extension that owns the property
* `name`: The actual property name within the extension
* `alias`: The external name used when referencing the subgraph (optional)
**Example:**
```json
{
"exposed_properties": [
{
"extension": "video_encoder",
"name": "bitrate_kbps",
"alias": "output_quality"
},
{
"extension": "frame_buffer",
"name": "max_buffer_size",
"alias": "buffer_limit"
},
{
"extension": "video_encoder",
"name": "codec_type"
}
]
}
```
In this example, external graphs can configure video quality through the `output_quality` alias, set buffer limits through `buffer_limit`, and specify codec type directly. The aliases provide user-friendly names while hiding implementation details. Exposed interfaces provide the following benefits:
* **Encapsulation**: Hide internal complexity while providing clean external APIs
* **Selective exposure**: Allow control over which interfaces and properties can be accessed externally
* **Interface documentation**: Serve as documentation for subgraph capabilities
## Using subgraphs [#using-subgraphs]
### Referencing subgraphs [#referencing-subgraphs]
To use a subgraph in your main graph, reference it as a node with type `subgraph`. The subgraph becomes part of your graph's namespace and can be connected like any other component.
**Basic subgraph reference:**
```json
{
"nodes": [
{
"type": "extension",
"name": "input_source",
"addon": "audio_input"
},
{
"type": "extension",
"name": "output_sink",
"addon": "audio_output"
},
{
"type": "subgraph",
"name": "audio_pipeline",
"source_uri": "./ten_packages/extension/audio_processing/subgraph.json"
}
],
"connections": [
{
"extension": "input_source",
"audio_frame": [
{
"name": "raw_audio",
"dest": [
{
"subgraph": "audio_pipeline"
}
]
}
]
},
{
"subgraph": "audio_pipeline",
"audio_frame": [
{
"name": "processed_audio",
"dest": [
{
"extension": "output_sink"
}
]
}
]
}
]
}
```
**Subgraph with properties:**
You can configure exposed properties when referencing a subgraph:
```json
{
"type": "subgraph",
"name": "video_processor",
"source_uri": "./subgraphs/video_enhancement.json",
"property": {
"output_quality": "high",
"buffer_limit": 1024,
"codec_type": "h264"
}
}
```
The properties you specify must exist in the subgraph's `exposed_properties` field, otherwise the configuration is rejected.
### Connection patterns [#connection-patterns]
Subgraphs support multiple connection patterns that provide flexibility in how you route data between components.
#### Direct connections to subgraphs [#direct-connections-to-subgraphs]
Connect directly to a subgraph using its name when the subgraph exposes the appropriate message interface:
```json
{
"connections": [
{
"extension": "data_source",
"cmd": [
{
"name": "process_data",
"dest": [
{
"subgraph": "data_processor"
}
]
}
]
}
]
}
```
This pattern works when `data_processor` exposes a `cmd_in` interface for `process_data`. The connection is clean and hides the subgraph's internal structure.
#### Connections to elements within subgraphs [#connections-to-elements-within-subgraphs]
For more granular control, connect directly to specific extensions within a subgraph using namespace syntax:
```json
{
"connections": [
{
"extension": "external_source",
"data": [
{
"name": "raw_input",
"dest": [
{
"extension": "video_pipeline:frame_buffer"
}
]
}
]
},
{
"extension": "video_pipeline:encoder",
"video_frame": [
{
"name": "encoded_output",
"dest": [
{
"extension": "external_sink"
}
]
}
]
}
]
}
```
The `subgraph_name:extension_name` syntax allows you to bypass the subgraph's exposed interfaces and connect directly to internal elements. Use this pattern when you need fine-grained control over data flow.
#### Advanced connection syntax [#advanced-connection-syntax]
You can mix different connection patterns within the same graph to create sophisticated data flow architectures:
```json
{
"connections": [
{
"extension": "input_hub",
"data": [
{
"name": "sensor_data",
"dest": [
{
"subgraph": "processing_pipeline"
},
{
"extension": "backup_pipeline:data_logger"
},
{
"extension": "monitoring_extension"
}
]
}
]
},
{
"subgraph": "processing_pipeline",
"cmd": [
{
"name": "status_update",
"dest": [
{
"extension": "monitoring_extension"
},
{
"subgraph": "alert_system"
}
]
}
]
}
]
}
```
**Connection pattern guidelines:**
* **Use direct subgraph connections** when working with well-defined interfaces
* **Use namespace syntax** when you need to access specific internal functionality
* **Mix patterns** when building complex data flow architectures
* **Prefer exposed interfaces** for maintainability and clarity
These patterns give you the flexibility to build everything from simple linear pipelines to complex multi-path processing architectures while maintaining clear organizational boundaries.
## Flattening process [#flattening-process]
### How flattening works [#how-flattening-works]
Before your application runs, the TEN framework automatically flattens all subgraph references into a single, standard graph structure. This flattening process converts the modular development structure into the standard graph format that the TEN runtime executes.
**Flattening transformations:**
1. **Namespace resolution**: Subgraph references are resolved and internal elements are prefixed with the subgraph name to ensure global uniqueness.
2. **Node expansion**: Subgraph nodes are replaced with their constituent extension nodes, maintaining all original properties and configurations.
3. **Connection mapping**: References using namespace syntax (`subgraph:extension`) are converted to direct extension names, and subgraph-level connections are mapped to specific internal extensions.
4. **Property inheritance**: Properties specified when referencing a subgraph are applied to the appropriate internal extensions based on the `exposed_properties` mapping.
5. **Interface removal**: The `exposed_messages` and `exposed_properties` metadata is removed since it's only needed during graph composition.
**Naming convention:**
The colon (`:`) is replaced with an underscore (`_`) to create valid extension names in the flattened structure.
* Original: `subgraph_name:extension_name`
* Flattened: `subgraph_name_extension_name`
### Flattened graph example [#flattened-graph-example]
Following is a complete example that shows how a graph with subgraphs transforms during the flattening process:
**Before flattening:**
```json
{
"nodes": [
{
"type": "extension",
"name": "input_source",
"addon": "microphone_input"
},
{
"type": "extension",
"name": "output_sink",
"addon": "speaker_output"
},
{
"type": "subgraph",
"name": "audio_pipeline",
"source_uri": "./subgraphs/audio_processing.json",
"property": {
"sample_rate": 48000,
"noise_threshold": 0.3
}
},
{
"type": "subgraph",
"name": "effects_chain",
"source_uri": "./subgraphs/audio_effects.json"
}
],
"connections": [
{
"extension": "input_source",
"audio_frame": [
{
"name": "raw_audio",
"dest": [
{
"subgraph": "audio_pipeline"
}
]
}
]
},
{
"extension": "audio_pipeline:processor",
"audio_frame": [
{
"name": "clean_audio",
"dest": [
{
"extension": "effects_chain:reverb"
}
]
}
]
},
{
"subgraph": "effects_chain",
"audio_frame": [
{
"name": "final_audio",
"dest": [
{
"extension": "output_sink"
}
]
}
]
}
]
}
```
**After flattening:**
```json
{
"nodes": [
{
"type": "extension",
"name": "input_source",
"addon": "microphone_input"
},
{
"type": "extension",
"name": "output_sink",
"addon": "speaker_output"
},
{
"type": "extension",
"name": "audio_pipeline_enhancer",
"addon": "audio_enhancement",
"property": {
"sample_rate": 48000
}
},
{
"type": "extension",
"name": "audio_pipeline_processor",
"addon": "noise_reduction",
"property": {
"noise_threshold": 0.3
}
},
{
"type": "extension",
"name": "effects_chain_reverb",
"addon": "reverb_effect"
},
{
"type": "extension",
"name": "effects_chain_compressor",
"addon": "dynamic_compressor"
}
],
"connections": [
{
"extension": "input_source",
"audio_frame": [
{
"name": "raw_audio",
"dest": [
{
"extension": "audio_pipeline_enhancer"
}
]
}
]
},
{
"extension": "audio_pipeline_enhancer",
"audio_frame": [
{
"name": "enhanced_audio",
"dest": [
{
"extension": "audio_pipeline_processor"
}
]
}
]
},
{
"extension": "audio_pipeline_processor",
"audio_frame": [
{
"name": "clean_audio",
"dest": [
{
"extension": "effects_chain_reverb"
}
]
}
]
},
{
"extension": "effects_chain_reverb",
"audio_frame": [
{
"name": "reverb_audio",
"dest": [
{
"extension": "effects_chain_compressor"
}
]
}
]
},
{
"extension": "effects_chain_compressor",
"audio_frame": [
{
"name": "final_audio",
"dest": [
{
"extension": "output_sink"
}
]
}
]
}
]
}
```
**Key changes during flattening:**
* **Subgraph nodes removed**: `audio_pipeline` and `effects_chain` subgraph nodes are replaced with their constituent extensions
* **Names prefixed**: Internal extensions get prefixed names (`audio_pipeline_processor`, `effects_chain_reverb`)
* **Properties distributed**: Subgraph properties are applied to the correct internal extensions
* **Connections resolved**: All namespace references and subgraph connections are converted to direct extension connections
* **Internal connections preserved**: Connections that existed within the original subgraphs are included in the flattened result
The flattened graph contains only standard extensions and direct connections, maintaining the organizational benefits you gained during development while using the standard TEN graph execution model.
## Advanced features [#advanced-features]
### Message conversion with subgraphs [#message-conversion-with-subgraphs]
Subgraphs support the TEN framework's message conversion mechanism, allowing you to transform message formats when routing data between components.
**Message conversion syntax:**
You can apply message conversion rules when connecting to subgraphs, internal subgraph elements, or when subgraphs connect to external components:
```json
{
"connections": [
{
"extension": "data_source",
"cmd": [
{
"name": "user_request",
"dest": [
{
"subgraph": "ai_processor",
"msg_conversion": {
"type": "per_property",
"rules": [
{
"path": "request_type",
"conversion_mode": "fixed_value",
"value": "chat_completion"
},
{
"path": "model_config.temperature",
"conversion_mode": "from_original",
"original_path": "settings.creativity"
}
],
"keep_original": true
}
},
{
"extension": "nlp_pipeline:tokenizer",
"msg_conversion": {
"type": "per_property",
"rules": [
{
"path": "input_text",
"conversion_mode": "from_original",
"original_path": "user_message"
}
],
"keep_original": false
}
}
]
}
]
}
]
}
```
**Conversion during flattening:**
Message conversion rules are preserved during the flattening process, ensuring that your data transformations work correctly at runtime:
```json
{
"connections": [
{
"extension": "data_source",
"cmd": [
{
"name": "user_request",
"dest": [
{
"extension": "ai_processor_chat_handler",
"msg_conversion": {
"type": "per_property",
"rules": [
{
"path": "request_type",
"conversion_mode": "fixed_value",
"value": "chat_completion"
}
],
"keep_original": true
}
},
{
"extension": "nlp_pipeline_tokenizer",
"msg_conversion": {
"type": "per_property",
"rules": [
{
"path": "input_text",
"conversion_mode": "from_original",
"original_path": "user_message"
}
],
"keep_original": false
}
}
]
}
]
}
]
}
```
### Multi-graph connections [#multi-graph-connections]
Beyond subgraphs, TEN supports connections between separate graphs running in the same application or across different applications. This enables sophisticated distributed architectures.
#### Connecting to predefined graphs [#connecting-to-predefined-graphs]
You can reference and connect to other predefined graphs within the same TEN application:
```json
{
"nodes": [
{
"type": "extension",
"name": "local_processor",
"addon": "data_processor"
},
{
"type": "graph",
"graph_name": "analytics_pipeline",
"singleton": true
},
{
"type": "graph",
"graph_name": "monitoring_system",
"singleton": false
}
],
"connections": [
{
"extension": "local_processor",
"data": [
{
"name": "processed_data",
"dest": [
{
"extension": "analytics_pipeline:data_ingester"
},
{
"graph": "monitoring_system"
}
]
}
]
},
{
"graph": "analytics_pipeline",
"cmd": [
{
"name": "analysis_complete",
"dest": [
{
"extension": "local_processor"
}
]
}
]
}
]
}
```
#### Remote graph connections [#remote-graph-connections]
Connect to graphs running in different TEN applications across the network:
```json
{
"nodes": [
{
"type": "extension",
"name": "local_service",
"addon": "api_handler"
},
{
"type": "graph",
"graph_name": "ml_inference",
"singleton": true,
"app": "msgpack://ml-server.example.com:8002/"
},
{
"type": "graph",
"graph_name": "data_warehouse",
"singleton": false,
"app": "msgpack://data-cluster.example.com:8003/"
}
],
"connections": [
{
"extension": "local_service",
"cmd": [
{
"name": "inference_request",
"dest": [
{
"graph": "ml_inference"
}
]
}
]
},
{
"graph": "ml_inference",
"data": [
{
"name": "inference_result",
"dest": [
{
"extension": "local_service"
},
{
"graph": "data_warehouse"
}
]
}
]
}
]
}
```
#### Graph node types [#graph-node-types]
TEN supports three types of graph nodes for different connection scenarios:
**Subgraph nodes:**
```json
{
"type": "subgraph",
"name": "processing_module",
"source_uri": "./subgraphs/data_processing.json",
"property": {
"batch_size": 100
}
}
```
**Local graph nodes:**
```json
{
"type": "graph",
"graph_name": "background_service",
"singleton": true
}
```
```json
{
"type": "graph",
"graph_name": "worker_pool",
"singleton": false
}
```
**Remote graph nodes:**
```json
{
"type": "graph",
"graph_name": "external_api",
"singleton": true,
"app": "msgpack://api.service.com:8001/"
}
```
```json
{
"type": "graph",
"graph_name": "distributed_cache",
"singleton": false,
"app": "msgpack://cache-cluster.internal:8004/"
}
```
**Graph node properties:**
* **graph\_name**: Identifies the target graph within its application
* **singleton**: Controls whether multiple instances can exist (`true` = single instance, `false` = multiple instances allowed)
* **app**: Specifies the target application URL for remote connections (omit for local graphs)
These advanced features enable you to build sophisticated distributed systems where subgraphs, local graphs, and remote services work together seamlessly while maintaining clear architectural boundaries.
## Reference [#reference]
### Graph node types [#graph-node-types-1]
TEN graphs support three types of node references that enable different architectural patterns and connection scopes.
#### Subgraph nodes [#subgraph-nodes]
Reference external graph files as reusable components within your graph:
**Basic subgraph node:**
```json
{
"type": "subgraph",
"name": "unique_subgraph_name",
"source_uri": "path/to/subgraph.json"
}
```
**Subgraph node with properties:**
```json
{
"type": "subgraph",
"name": "configurable_module",
"source_uri": "./ten_packages/extension/module/subgraph.json",
"property": {
"param1": "value1",
"param2": 42,
"nested_config": {
"enabled": true,
"threshold": 0.85
}
}
}
```
**Remote subgraph node:**
```json
{
"type": "subgraph",
"name": "shared_component",
"source_uri": "https://cdn.example.com/subgraphs/v1.2/nlp_pipeline.json"
}
```
**Required fields:**
* `type`: Must be `"subgraph"`
* `name`: Unique identifier within the current graph (serves as namespace)
* `source_uri`: Path or URL to the subgraph definition file
**Optional fields:**
* `property`: Configuration values for exposed properties
#### Local graph nodes [#local-graph-nodes]
Reference other predefined graphs within the same TEN application:
**Singleton local graph:**
```json
{
"type": "graph",
"graph_name": "predefined_graph_id",
"singleton": true
}
```
**Multi-instance local graph:**
```json
{
"type": "graph",
"graph_name": "worker_template",
"singleton": false
}
```
**Required fields:**
* `type`: Must be `"graph"`
* `graph_name`: Identifier of the predefined graph in the same application
* `singleton`: Boolean controlling instance multiplicity
**Singleton behavior:**
* `true`: Only one instance of the graph exists, shared across references
* `false`: Each reference creates a separate graph instance
#### Remote graph nodes [#remote-graph-nodes]
Reference graphs running in different TEN applications across the network:
**Singleton remote graph:**
```json
{
"type": "graph",
"graph_name": "ml_service",
"singleton": true,
"app": "msgpack://ml-cluster.internal:8002/"
}
```
**Multi-instance remote graph:**
```json
{
"type": "graph",
"graph_name": "data_processor",
"singleton": false,
"app": "msgpack://processing-farm.example.com:8003/"
}
```
**Required fields:**
* `type`: Must be `"graph"`
* `graph_name`: Identifier of the target graph in the remote application
* `singleton`: Boolean controlling instance behavior
* `app`: Network address of the target TEN application
**Supported protocols:**
* `msgpack://host:port/`: MessagePack-based communication
* Additional protocols may be supported in future versions
### Connection syntax reference [#connection-syntax-reference]
TEN provides flexible syntax for establishing connections between different types of graph nodes and their internal elements.
#### subgraph\_name:element_name [#subgraph_name]
Access specific elements within subgraphs using namespace syntax:
**Extension within subgraph:**
```json
{
"extension": "audio_pipeline:noise_filter",
"audio_frame": [
{
"name": "filtered_audio",
"dest": [
{
"extension": "output_device"
}
]
}
]
}
```
**Connecting to subgraph element:**
```json
{
"extension": "microphone",
"audio_frame": [
{
"name": "raw_audio",
"dest": [
{
"extension": "processing_chain:input_buffer"
}
]
}
]
}
```
**Syntax rules:**
* Format: `subgraph_name:extension_name`
* The colon (`:`) separates namespace from element name
* Element name must exist within the referenced subgraph
* Bypasses subgraph's exposed interface constraints
#### Graph references [#graph-references]
Connect to graphs using their identifiers:
**Direct graph connection:**
```json
{
"subgraph": "local_processor",
"cmd": [
{
"name": "process_complete",
"dest": [
{
"graph": "analytics_engine"
}
]
}
]
}
```
**Graph element connection:**
```json
{
"graph": "background_service",
"data": [
{
"name": "status_update",
"dest": [
{
"extension": "monitor:logger"
}
]
}
]
}
```
**Local graph with namespace:**
```json
{
"extension": "coordinator",
"cmd": [
{
"name": "task_assignment",
"dest": [
{
"extension": "worker_pool:task_manager"
}
]
}
]
}
```
#### Advanced patterns [#advanced-patterns]
Combine multiple connection types for sophisticated data flow architectures:
**Multi-destination routing:**
```json
{
"extension": "data_ingester",
"data": [
{
"name": "incoming_data",
"dest": [
{
"subgraph": "validation_pipeline"
},
{
"extension": "processing_farm:load_balancer"
},
{
"graph": "audit_logger"
},
{
"extension": "backup_storage"
}
]
}
]
}
```
**Cross-application message flow:**
```json
{
"graph": "local_analytics",
"cmd": [
{
"name": "analysis_request",
"dest": [
{
"extension": "ml_cluster:inference_engine"
}
]
}
]
}
```
**Conditional routing with message conversion:**
```json
{
"extension": "request_router",
"cmd": [
{
"name": "user_request",
"dest": [
{
"subgraph": "fast_processing",
"msg_conversion": {
"type": "per_property",
"rules": [
{
"path": "priority",
"conversion_mode": "fixed_value",
"value": "high"
}
]
}
},
{
"extension": "slow_processing:queue_manager",
"msg_conversion": {
"type": "per_property",
"rules": [
{
"path": "priority",
"conversion_mode": "fixed_value",
"value": "normal"
}
]
}
}
]
}
]
}
```
**Connection target types:**
* `extension`: Direct extension name
* `subgraph`: Subgraph name (uses exposed interfaces)
* `graph`: Graph name (uses exposed interfaces)
* `extension` with namespace: `namespace:extension_name`
**Best practices:**
* Use exposed interfaces when possible for better encapsulation
* Use namespace syntax only when you need direct access to internal elements
* Prefer subgraphs over direct graph references for reusable components
* Document complex connection patterns for maintainability
# Send images to the agent (/en/ai/build/send-multimodal-messages)
When interacting with an agent, you may need to upload images or send image messages from the client to help the agent better understand the user's intent. This page describes how to use the Conversational AI Engine toolkit to send image messages to the large language model from your app. The LLM can then automatically reference the image content in subsequent conversations and generate more relevant responses.
## Understand the tech [#understand-the-tech]
Agora provides a flexible, scalable, and standardized conversational AI engine toolkit. The toolkit supports **iOS**, **Android**, and **Web** platforms, and encapsulates scenario-based APIs. You can use these APIs to integrate the capabilities of the [Agora Signaling SDK](/en/realtime-media/rtm) and [Agora Video SDK](../../realtime-media/video/quickstart) to enable the following features:
* [Interrupt agents](shape-the-conversation/interrupt-agent)
* [Display live transcripts](transcripts)
* [Client-side events](handle-runtime-events/event-notifications)
* [Set optimal audio parameters](../best-practices/audio-setup) for iOS and Android
* [Send picture messages](send-multimodal-messages)
Call the toolkit's `chat` API to send a picture message, and listen to `onMessageReceiptUpdated` to receive the picture message receipt.
## Prerequisites [#prerequisites]
Before you begin, ensure the following:
* You have implemented the Conversational AI Engine [quickstart](../get-started/quickstart).
* Your app integrates Agora Video SDK v4.5.1 or later and includes the [Video SDK quickstart](../../realtime-media/video/quickstart).
* You have enabled Signaling in the Agora Console and completed the [Signaling quickstart](/en/realtime-media/rtm/quickstart) for basic messaging.
* You maintain active and authenticated RTC and Signaling instances that persist beyond the component lifecycle. The toolkit does not manage RTC or Signaling initialization, lifecycle, or authentication.
Info
* Picture messaging is currently in beta and free for a limited time.
* Image processing depends on the capabilities of the integrated LLM. Make sure the LLM you connect to Conversational AI Engine supports image input.
## Implementation [#implementation]
This section explains how to send a picture message from your app.
Android
iOS
Web
1. **Integrate the toolkit**
Copy the [`convoaiApi`](https://github.com/AgoraIO-Community/Conversational-AI-Demo/tree/main/Android/scenes/convoai/src/main/java/io/agora/scene/convoai/convoaiApi) folder to your project and import the toolkit before calling the toolkit API. Refer to [Folder structure](#reference) to understand the role of each file.
2. **Create a toolkit instance**
Create a configuration object with the Video SDK and Signaling engine instances. Use the configuration to create a toolkit instance.
```kotlin
// Create configuration objects for the RTC and RTM instances
val config = ConversationalAIAPIConfig(
rtcEngine = rtcEngineInstance,
rtmClient = rtmClientInstance,
enableLog = true
)
// Create component instance
val api = ConversationalAIAPIImpl(config)
```
3. **Register callback**
```kotlin
api.addHandler(covEventHandler)
```
4. **Subscribe to the channel**
Agent-related events are delivered through Signaling channel messages. To receive these events, call `subscribeMessage` before starting the agent session.
```kotlin
api.subscribeMessage("channelName") { error ->
if (error != null) {
// Handle error
}
}
```
5. **Add a conversational AI agent to the channel**
To [start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join), configure the following parameters in your `POST` request:
| Parameter | Description | Required |
| --------------------------------------- | -------------------------------------------------- | -------- |
| `advanced_features.enable_rtm: true` | Starts the Signaling service | Yes |
| `parameters.data_channel: "rtm"` | Enables Signaling as the data transmission channel | Yes |
| `parameters.enable_metrics: true` | Enables agent performance data collection | Optional |
| `parameters.enable_error_message: true` | Enables reporting of agent error events | Optional |
After a successful response, the agent joins the specified RTC channel and is ready to interact with the user.
6. **Send an image**
```kotlin
val uuid = "unique-image-id-123"
val imageUrl = "https://example.com/image.jpg"
api.chat("agentUserId", ImageMessage(uuid = uuid, imageUrl = imageUrl)) { error ->
if (error != null) {
Log.e("Chat", "Failed to send image: ${error.errorMessage}")
} else {
Log.d("Chat", "Image send request successful")
}
}
```
Info
The `chat` completion callback only indicates whether the sending request is successful, and does not reflect the actual processing status of the message.
7. **Handle image sending status**
Image send success is confirmed by `onMessageReceiptUpdated`. If sending fails, `onMessageError` is triggered. Use the `uuid` value in the callback to identify the uploaded picture.
Image sent successfully:
```kotlin
override fun onMessageReceiptUpdated(agentUserId: String, receipt: MessageReceipt) {
if (receipt.chatMessageType == ChatMessageType.Image) {
try {
val json = JSONObject(receipt.message)
if (json.has("uuid")) {
val receivedUuid = json.getString("uuid")
if (receivedUuid == "your-sent-uuid") {
Log.d("ImageSend", "Image sent successfully: $receivedUuid")
}
}
} catch (e: Exception) {
Log.e("ImageSend", "Failed to parse message receipt: ${e.message}")
}
}
}
```
Image sending failed:
```kotlin
override fun onMessageError(agentUserId: String, error: MessageError) {
if (error.chatMessageType == ChatMessageType.Image) {
try {
val json = JSONObject(error.message)
if (json.has("uuid")) {
val failedUuid = json.getString("uuid")
if (failedUuid == "your-sent-uuid") {
Log.e("ImageSend", "Image send failed: $failedUuid")
}
}
} catch (e: Exception) {
Log.e("ImageSend", "Failed to parse error message: ${e.message}")
}
}
}
```
8. **Unsubscribe from the channel**
```kotlin
api.unsubscribeMessage("channelName") { error ->
if (error != null) {
// Handle the error
}
}
```
9. **Release resources**
```kotlin
api.destroy()
```
1. **Integrate the toolkit**
Copy the [`ConversationalAIAPI`](https://github.com/AgoraIO-Community/Conversational-AI-Demo/tree/main/iOS/Scenes/ConvoAI/ConvoAI/ConvoAI/Classes/ConversationalAIAPI) folder to your project and import the toolkit before calling the toolkit APIs. Refer to [Folder structure](#reference) to understand the role of each file.
2. **Create a toolkit instance**
Create a configuration object with the Video SDK and Signaling engine instances, then use the configuration to create a toolkit instance.
```swift
let config = ConversationalAIAPIConfig(
rtcEngine: rtcEngine,
rtmEngine: rtmEngine,
enableLog: true
)
convoAIAPI = ConversationalAIAPIImpl(config: config)
```
3. **Subscribe to the channel**
Agent-related events are delivered through Signaling channel messages. To receive these events, call `subscribeMessage` before starting the agent session.
```swift
convoAIAPI.subscribeMessage(channelName: channelName) { error in
if let error = error {
print("Subscription failed: \(error.message)")
} else {
print("Subscription successful")
}
}
```
4. **Register callback**
```swift
convoAIAPI.addHandler(handler: self)
```
5. **Add a conversational AI agent to the channel**
To [start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join), configure the following parameters in your `POST` request:
| Parameter | Description | Required |
| --------------------------------------- | -------------------------------------------------- | -------- |
| `advanced_features.enable_rtm: true` | Starts the Signaling service | Yes |
| `parameters.data_channel: "rtm"` | Enables Signaling as the data transmission channel | Yes |
| `parameters.enable_metrics: true` | Enables agent performance data collection | Optional |
| `parameters.enable_error_message: true` | Enables reporting of agent error events | Optional |
6. **Send an image**
```swift
let uuid = UUID().uuidString
let imageUrl = "https://example.com/image.jpg"
let message = ImageMessage(uuid: uuid, url: imageUrl)
self.convoAIAPI.chat(agentUserId: "\(agentUid)", message: message) { [weak self] error in
if let error = error {
print("send image failed, error: \(error.message)")
} else {
print("send image success")
}
}
```
Info
The `chat` completion callback only indicates whether the sending request is successful, and does not reflect the actual processing status of the message.
7. **Receive image sending response**
Success is confirmed by `onMessageReceiptUpdated`. If sending fails, `onMessageError` is triggered.
Image sent successfully:
```swift
struct PictureInfo: Codable {
let uuid: String
}
public func onMessageReceiptUpdated(agentUserId: String, messageReceipt: MessageReceipt) {
if messageReceipt.type == .context {
guard let messageData = messageReceipt.message.data(using: .utf8) else {
return
}
do {
let imageInfo = try JSONDecoder().decode(PictureInfo.self, from: messageData)
self.messageView.viewModel.updateImageMessage(uuid: imageInfo.uuid, state: .success)
} catch {
print("Failed to decode PictureInfo: \(error)")
}
}
}
```
Image sending failed:
```swift
struct ImageUploadError: Codable {
let code: Int
let message: String
}
struct ImageUploadErrorResponse: Codable {
let uuid: String
let success: Bool
let error: ImageUploadError?
}
public func onMessageError(agentUserId: String, error: MessageError) {
if let messageData = error.message.data(using: .utf8) {
do {
let errorResponse = try JSONDecoder().decode(ImageUploadErrorResponse.self, from: messageData)
if !errorResponse.success {
DispatchQueue.main.async { [weak self] in
self?.messageView.viewModel.updateImageMessage(uuid: errorResponse.uuid, state: .failed)
}
}
} catch {
addLog("Failed to parse error message JSON: \(error)")
}
}
}
```
8. **Unsubscribe from the channel**
```swift
convoAIAPI.unsubscribeMessage(channelName: channelName) { error in
if let error = error {
print("Unsubscription failed: \(error.message)")
} else {
print("Unsubscribed successfully")
}
}
```
9. **Release resources**
```swift
convoAIAPI.destroy()
```
1. **Integrate the toolkit**
Copy the [`conversational-ai-api`](https://github.com/AgoraIO-Community/Conversational-AI-Demo/tree/main/Web/Scenes/VoiceAgent/src/conversational-ai-api) file to your project and import the toolkit before calling its API. Refer to [Folder structure](#reference) to understand the role of each file.
2. **Create a toolkit instance**
Before joining an RTC channel, create Video SDK and Signaling engine instances and pass them into the toolkit instance.
```ts
ConversationalAIAPI.init({
rtcEngine,
rtmEngine,
})
const conversationalAIAPI = ConversationalAIAPI.getInstance()
```
3. **Subscribe to the channel**
Agent-related events are delivered through Signaling messages. Before starting an agent session, call `subscribeMessage` to receive these events.
```ts
conversationalAIAPI.subscribeMessage(channel_name)
```
4. **Register callbacks**
```ts
conversationalAIAPI.on(EConversationalAIAPIEvents.MESSAGE_RECEIPT_UPDATED, handleMessageReceiptUpdated)
conversationalAIAPI.on(EConversationalAIAPIEvents.MESSAGE_ERROR, onAgentError)
```
5. **Add a conversational AI agent to the channel**
To [start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join), configure the following parameters in your `POST` request:
| Parameter | Description | Required |
| --------------------------------------- | -------------------------------------------------- | -------- |
| `advanced_features.enable_rtm: true` | Starts the Signaling service | Yes |
| `parameters.data_channel: "rtm"` | Enables Signaling as the data transmission channel | Yes |
| `parameters.enable_metrics: true` | Enables agent performance data collection | Optional |
| `parameters.enable_error_message: true` | Enables reporting of agent error events | Optional |
6. **Send an image**
```ts
import { EChatMessageType } from '@/conversational-ai-api/type'
await conversationalAIAPI.chat(`${agent_rtc_uid}`, {
messageType: EChatMessageType.IMAGE,
url: 'https://example.com/image.jpg',
uuid: genUUID()
})
```
Info
The `chat` completion callback only indicates whether the sending request is successful, and does not reflect the actual processing status of the message.
7. **Receive image sending response**
Image sent successfully:
```ts
conversationalAIAPI.on(EConversationalAIAPIEvents.MESSAGE_RECEIPT_UPDATED, (agentUserId: string, messageReceipt: TMessageReceipt) => {
if (messageReceipt.moduleType !== EModuleType.CONTEXT) {
return
}
try {
const receiptMessage = JSON.parse(messageReceipt.message)
const uuid = receiptMessage.uuid
if (!uuid) return
console.log(`Message sent successfully, UUID: ${uuid}`)
} catch (error) {
console.error('Failed to parse message:', error)
}
})
```
Image sending failed:
```ts
conversationalAIAPI.on(EConversationalAIAPIEvents.MESSAGE_ERROR, (agentUserId, error) => {
console.error(`Message error for agent ${agentUserId}:`, error)
if (error.type === EChatMessageType.IMAGE) {
try {
const errorData = JSON.parse(error.message)
if (errorData?.uuid) {
console.warn(`Image error for agent ${agentUserId} with UUID: ${errorData.uuid}`)
}
} catch (e) {
console.error(`Failed to handle image error for agent ${agentUserId}:`, e)
}
}
})
```
8. **Unsubscribe from the channel**
```ts
conversationalAIAPI.unsubscribeMessage(channel_name)
```
9. **Release resources**
```ts
conversationalAIAPI.destroy()
```
## Reference [#reference]
This section contains supporting information that completes the guidance on this page.
Android
iOS
Web
**Folder structure**
* `IConversationalAIAPI.kt`: API interface, related data structures, and enumerations
* `ConversationalAIAPIImpl.kt`: main implementation logic
* `ConversationalAIUtils.kt`: utility functions and callback management
* `v3/`
* `TranscriptionController.kt`: transcript rendering and synchronization
* `MessageParser.kt`: transcription and message parsing
**API reference**
* [`addHandler`](/en/api-reference/api-ref/conversational-ai/client-toolkit/android#addhandler)
* [`subscribeMessage`](/en/api-reference/api-ref/conversational-ai/client-toolkit/android#subscribemessage)
* [`unsubscribeMessage`](/en/api-reference/api-ref/conversational-ai/client-toolkit/android#unsubscribemessage)
* [`destroy`](/en/api-reference/api-ref/conversational-ai/client-toolkit/android#destroy)
* [`onMessageReceiptUpdated`](/en/api-reference/api-ref/conversational-ai/client-toolkit/android#onmessagereceiptupdated)
* [`onMessageError`](/en/api-reference/api-ref/conversational-ai/client-toolkit/android#onmessageerror)
* [`chat`](/en/api-reference/api-ref/conversational-ai/client-toolkit/android#chat)
**Folder structure**
* `ConversationalAIAPI.swift`: API interface, data structures, and enumerations
* `ConversationalAIAPIImpl.swift`: main implementation logic
* `Transcription/`
* `TranscriptionController.swift`: transcript rendering and transcription control
**API reference**
* [`addHandler`](/en/api-reference/api-ref/conversational-ai/client-toolkit/ios#addhandler)
* [`subscribeMessage`](/en/api-reference/api-ref/conversational-ai/client-toolkit/ios#subscribemessage)
* [`unsubscribeMessage`](/en/api-reference/api-ref/conversational-ai/client-toolkit/ios#unsubscribemessage)
* [`destroy`](/en/api-reference/api-ref/conversational-ai/client-toolkit/ios#destroy)
* [`onMessageReceiptUpdated`](/en/api-reference/api-ref/conversational-ai/client-toolkit/ios#onmessagereceiptupdated)
* [`onMessageError`](/en/api-reference/api-ref/conversational-ai/client-toolkit/ios#onmessageerror)
* [`chat`](/en/api-reference/api-ref/conversational-ai/client-toolkit/ios#chat)
**Folder structure**
* `index.ts`: main API class
* `type.ts`: API interfaces, data structures, and enumerations
* `utils/`
* `index.ts`: general utility functions
* `events.ts`: event management
* `sub-render.ts`: transcript rendering module
**API reference**
* [`IConversationalAIAPIEventHandler`](/en/api-reference/api-ref/conversational-ai/client-toolkit/web#iconversationalaiapieventhandlers-interface)
* [`EConversationalAIAPIEvents`](/en/api-reference/api-ref/conversational-ai/client-toolkit/web#econversationalaiapievents)
* [`subscribeMessage`](/en/api-reference/api-ref/conversational-ai/client-toolkit/web#subscribemessage)
* [`unsubscribeMessage`](/en/api-reference/api-ref/conversational-ai/client-toolkit/web#unsubscribe)
* [`destroy`](/en/api-reference/api-ref/conversational-ai/client-toolkit/web#destroy)
* [`chat`](/en/api-reference/api-ref/conversational-ai/client-toolkit/web#chat)
# Start and stop an agent (/en/ai/build/start-stop-agent)
This page describes how to call the Conversational AI Engine APIs to start and stop an AI agent.
## Understand the tech [#understand-the-tech]
A user joins a channel and triggers your business server to start an agent via the Conversational AI Engine API. The agent joins the same channel and communicates with the user through voice, using the specified LLM, TTS service, and Agora’s low-latency Software-Defined Real-Time Network (SDRTN®). When the conversation ends, the business server stops the agent and the user leaves the channel.
Conversational AI Engine workflow

## Prerequisites [#prerequisites]
Use the [Agora CLI](/en/introduction/agora-cli) to create or select an Agora project, enable RTC and Conversational AI, verify project readiness, and export local credentials:
```bash
agora login
agora project create conv-ai-tutorial --feature rtc --feature convoai
agora project use conv-ai-tutorial
agora project doctor --feature convoai
agora project env --format shell --with-secrets
```
You also need:
* A client app that can join an Agora RTC channel, such as the [Voice Calling](../../realtime-media/voice/quickstart) or [Video Calling](../../realtime-media/video/quickstart) quickstart.
## Install the SDK [#install-the-sdk]
Select your preferred language and install the corresponding SDK.
Python SDK
TypeScript SDK
Go SDK
```sh
pip install agora-agents
```
```sh
npm install agora-agents
```
```sh
go get github.com/AgoraIO/agora-agents-go/v2@v2.2.0
```
## Implementation [#implementation]
This section introduces the basic RESTful API requests you use to start and stop a conversational AI agent. In a production environment, implement these requests on your business server.
### Start a conversational AI agent [#start-a-conversational-ai-agent]
Call the `join` endpoint to create an agent instance that joins an Agora channel.
Python SDK
TypeScript SDK
Go SDK
REST API
```python
from agora_agent import Agent, Agora, Area, DeepgramSTT, OpenAI, MicrosoftTTS
client = Agora(
area=Area.US,
app_id='your-app-id',
app_certificate='your-app-certificate',
)
agent = (
Agent(client)
.with_stt(DeepgramSTT(model='nova-3', language='en-US'))
.with_llm(OpenAI(
api_key='your-llm-api-key',
base_url='https://api.openai.com/v1/chat/completions',
model='gpt-4o-mini',
system_messages=[{'role': 'system', 'content': 'You are a helpful chatbot.'}],
greeting_message='Hello, how can I help you?',
failure_message="Sorry, I don't know how to answer this question.",
max_history=10,
))
.with_tts(MicrosoftTTS(
key='your-tts-api-key',
region='eastus',
voice_name='en-US-AndrewMultilingualNeural',
))
)
session = agent.create_session(
channel='your_channel_name',
agent_uid='0',
remote_uids=['1002'],
name='unique_name',
idle_timeout=120,
)
agent_id = session.start()
print(f'Agent started: {agent_id}')
```
```typescript
import { Agent, AgoraClient, Area, DeepgramSTT, OpenAI, MicrosoftTTS } from 'agora-agents';
const client = new AgoraClient({
area: Area.US,
appId: 'your-app-id',
appCertificate: 'your-app-certificate',
});
const agent = new Agent({ client })
.withStt(new DeepgramSTT({ model: 'nova-3', language: 'en-US' }))
.withLlm(new OpenAI({
apiKey: 'your-llm-api-key',
url: 'https://api.openai.com/v1/chat/completions',
model: 'gpt-4o-mini',
systemMessages: [{ role: 'system', content: 'You are a helpful chatbot.' }],
greetingMessage: 'Hello, how can I help you?',
failureMessage: 'Sorry, I don't know how to answer this question.',
maxHistory: 10,
}))
.withTts(new MicrosoftTTS({
key: 'your-tts-api-key',
region: 'eastus',
voiceName: 'en-US-AndrewMultilingualNeural',
}));
const session = agent.createSession({
channel: 'your_channel_name',
agentUid: '0',
remoteUids: ['1002'],
name: 'unique_name',
idleTimeout: 120,
});
const agentId = await session.start();
console.log('Agent started:', agentId);
```
```go
package main
import (
"context"
"fmt"
"log"
Agora "github.com/AgoraIO/agora-agents-go/v2"
"github.com/AgoraIO/agora-agents-go/v2/agentkit"
"github.com/AgoraIO/agora-agents-go/v2/agentkit/vendors"
"github.com/AgoraIO/agora-agents-go/v2/option"
)
func main() {
ctx := context.Background()
idleTimeout := 120
client := agentkit.NewAgoraClient(agentkit.AgoraClientOptions{
Area: option.AreaUS,
AppID: "your-app-id",
AppCertificate: "your-app-certificate",
})
agent := agentkit.NewAgent(client).WithStt(
vendors.NewDeepgramSTT(vendors.DeepgramSTTOptions{
Model: "nova-3",
Language: "en-US",
}),
).WithLlm(
vendors.NewOpenAI(vendors.OpenAIOptions{
APIKey: "your-llm-api-key",
URL: "https://api.openai.com/v1/chat/completions",
Model: "gpt-4o-mini",
SystemMessages: []map[string]interface{}{
{"role": "system", "content": "You are a helpful chatbot."},
},
GreetingMessage: "Hello, how can I help you?",
FailureMessage: "Sorry, I don't know how to answer this question.",
MaxHistory: Agora.Int(10),
}),
).WithTts(
vendors.NewMicrosoftTTS(vendors.MicrosoftTTSOptions{
Key: "your-tts-api-key",
Region: "eastus",
VoiceName: "en-US-AndrewMultilingualNeural",
}),
)
session := agent.CreateSession(agentkit.CreateSessionOptions{
Channel: "your_channel_name",
AgentUID: "0",
RemoteUIDs: []string{"1002"},
Name: "unique_name",
IdleTimeout: &idleTimeout,
})
agentID, err := session.Start(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Println("Agent started:", agentID)
}
```
For the Curl request, pass in the `channel` name and `token` for agent authentication. To generate your base64-encoded credentials, see [RESTful authentication](/en/api-reference/api-ref/conversational-ai/authentication).
```bash
curl --request POST \
--url https://api.agora.io/api/conversational-ai-agent/v2/projects/:appid/join \
--header 'Authorization: Basic ' \
--header 'Content-Type: application/json' \
--data '
{
"name": "unique_name",
"properties": {
"channel": "",
"token": "",
"agent_rtc_uid": "0",
"remote_rtc_uids": ["1002"],
"enable_string_uid": false,
"idle_timeout": 120,
"llm": {
"url": "https://api.openai.com/v1/chat/completions",
"api_key": "",
"system_messages": [
{
"role": "system",
"content": "You are a helpful chatbot."
}
],
"greeting_message": "Hello, how can I help you?",
"failure_message": "Sorry, I don't know how to answer this question.",
"max_history": 10,
"params": {
"model": "gpt-4o-mini"
}
},
"asr": {
"language": "en-US"
},
"tts": {
"vendor": "microsoft",
"params": {
"key": "",
"region": "eastus",
"voice_name": "en-US-AndrewMultilingualNeural"
}
}
}
}'
```
If the request is successful, you receive the following response:
```json
// 200 OK
{
"agent_id": "1NT29X10YHxxxxxWJOXLYHNYB",
"create_ts": 1737111452,
"status": "RUNNING"
}
```
If the request is successful, the agent ID is returned. Store the agent ID to manage the agent in subsequent calls.
### Stop the conversational AI agent [#stop-the-conversational-ai-agent]
To end the conversation with the AI agent, call the `leave` endpoint. This causes the agent to leave the Agora channel.
Python SDK
TypeScript SDK
Go SDK
REST API
```python
session.stop()
```
```typescript
await session.stop();
```
```go
if err := session.Stop(ctx); err != nil {
log.Fatal(err)
}
```
```bash
curl --request POST \
--url https://api.agora.io/api/conversational-ai-agent/v2/projects/:appid/agents/:agentId/leave \
--header 'Authorization: Basic '
```
If the request is successful, the server responds with a `200 OK` status and an empty JSON object.
```json
// 200 OK
{}
```
Info
The number of Peak Concurrent Users (PCU) allowed to call the server API under a single App ID is limited to 20. If you need to increase this limit, please [contact technical support](mailto\:support@agora.io).
## 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.
### API reference [#api-reference]
* [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join)
* [Stop a conversational AI agent](/en/api-reference/api-ref/conversational-ai/leave)
* [Update agent configuration](/en/api-reference/api-ref/conversational-ai/update)
* [Query agent status](/en/api-reference/api-ref/conversational-ai/query)
* [Retrieve a list of agents](/en/api-reference/api-ref/conversational-ai/list)
# Display live transcripts (/en/ai/build/transcripts)
When interacting with conversational AI in real time, you can enable live transcripts to display the conversation content. This page explains how to implement live transcripts in your app.
## Understand the tech [#understand-the-tech]
Agora provides a flexible, scalable, and standardized conversational AI engine toolkit. The toolkit supports **iOS**, **Android**, and **Web** platforms, and encapsulates scenario-based APIs. You can use these APIs to integrate the capabilities of the [Agora Signaling SDK](/en/realtime-media/rtm) and [Agora Video SDK](../../realtime-media/video/quickstart) to enable the following features:
* [Interrupt agents](shape-the-conversation/interrupt-agent)
* [Display live transcripts](transcripts)
* [Client-side events](handle-runtime-events/event-notifications)
* [Set optimal audio parameters](../best-practices/audio-setup) for iOS and Android
* [Send picture messages](send-multimodal-messages)
The toolkit receives transcript content through the `onTranscriptUpdated` callback and supports monitoring the following transcript data types:
* **Agent transcript**: The agent's speech, including streaming updates and final results.
* **User transcript**: The user's speech, including real-time display and status management.
* **Transcript status**: State changes such as in progress, completed, or interrupted.
The following diagram outlines the step-by-step process to integrate live transcript functionality into your application:
Transcript rendering workflow

## Prerequisites [#prerequisites]
Before you begin, ensure the following:
* You have implemented the Conversational AI Engine [quickstart](../get-started/quickstart).
* Your app integrates Agora Video SDK v4.5.1 or later and includes the [Video SDK quickstart](../../realtime-media/video/quickstart).
* You have enabled Signaling in the Agora Console and completed the [Signaling quickstart](/en/realtime-media/rtm/quickstart) for basic messaging.
* You maintain active and authenticated RTC and Signaling instances that persist beyond the component lifecycle. The toolkit does not manage RTC or Signaling initialization, lifecycle, or authentication.
## Implementation [#implementation]
This section describes how to receive transcript content from the transcript processing module and display it in your app UI.
Android
iOS
Web
1. **Integrate the toolkit**
Copy the [`convoaiApi`](https://github.com/AgoraIO-Community/Conversational-AI-Demo/tree/main/Android/scenes/convoai/src/main/java/io/agora/scene/convoai/convoaiApi) folder to your project and import the toolkit before calling the toolkit API. Refer to [Folder structure](#reference) to understand the role of each file.
2. **Create a toolkit instance**
Create a configuration object with the Video SDK and Signaling engine instances. Set the transcript rendering mode, then use the configuration to create a toolkit instance.
```kotlin
// Create configuration objects for the RTC and RTM instances
val config = ConversationalAIAPIConfig(
rtcEngine = rtcEngineInstance,
rtmClient = rtmClientInstance,
// Set the transcript rendering mode. Options:
// - TranscriptRenderMode.Word: render transcript word by word.
// - TranscriptRenderMode.Text: render the full sentence at once.
renderMode = TranscriptRenderMode.Word,
enableLog = true
)
// Create component instance
val api = ConversationalAIAPIImpl(config)
```
3. **Subscribe to the channel**
Transcript data is delivered through Signaling channel messages. To receive transcript data, call `subscribeMessage` before starting the agent session.
```kotlin
api.subscribeMessage("channelName") { error ->
if (error != null) {
// Handle error
}
}
```
4. **Receive transcript**
Call the `addHandler` method to register your implementation of the transcription callback.
```kotlin
api.addHandler(covEventHandler)
```
5. **Implement UI rendering logic**
Inherit your UI module from the `IConversationalAIAPIEventHandler` interface. Implement the `onTranscriptUpdated` method to handle transcript rendering to the UI.
```kotlin
private val covEventHandler = object : IConversationalAIAPIEventHandler {
override fun onTranscriptUpdated(agentUserId: String, transcript: Transcript) {
// Handle transcript data and update the UI here
}
}
```
6. **Add a conversational AI agent to the channel**
To [start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join), configure the following parameters in your `POST` request:
| Parameter | Description | Required |
| --------------------------------------- | -------------------------------------------------- | -------- |
| `advanced_features.enable_rtm: true` | Starts the Signaling service | Yes |
| `parameters.data_channel: "rtm"` | Enables Signaling as the data transmission channel | Yes |
| `parameters.enable_metrics: true` | Enables agent performance data collection | Optional |
| `parameters.enable_error_message: true` | Enables reporting of agent error events | Optional |
After a successful response, the agent joins the specified RTC channel and is ready to interact with the user.
7. **Unsubscribe from the channel**
After an agent session ends, unsubscribe from channel messages to release transcription resources.
```kotlin
api.unsubscribeMessage("channelName") { error ->
if (error != null) {
// Handle the error
}
}
```
8. **Release resources**
At the end of each call, use the `destroy` method to clean up the cache.
```kotlin
api.destroy()
```
1. **Integrate the toolkit**
Copy the [`ConversationalAIAPI`](https://github.com/AgoraIO-Community/Conversational-AI-Demo/tree/main/iOS/Scenes/ConvoAI/ConvoAI/ConvoAI/Classes/ConversationalAIAPI) folder to your project and import the toolkit before calling the toolkit APIs. Refer to [Folder structure](#reference) to understand the role of each file.
2. **Create a toolkit instance**
Create a configuration object with the Video SDK and Signaling engine instances. Set the transcript rendering mode, then use the configuration to create a toolkit instance.
```swift
// Create a configuration object for the RTC and RTM instances
let config = ConversationalAIAPIConfig(
rtcEngine: rtcEngine,
rtmEngine: rtmEngine,
/**
* Set the transcript rendering mode. Available options:
* - .words: Word-by-word rendering mode.
* - .text: Sentence-by-sentence rendering mode.
*/
renderMode: .words,
enableLog: true
)
// Create the component instance
convoAIAPI = ConversationalAIAPIImpl(config: config)
```
3. **Subscribe to the channel**
Transcript data is delivered through Signaling channel messages. To receive transcript data, call `subscribeMessage` before starting the agent session.
```swift
convoAIAPI.subscribeMessage(channelName: channelName) { error in
if let error = error {
print("Subscription failed: \(error.message)")
} else {
print("Subscription successful")
}
}
```
4. **Receive transcript**
Call the `addHandler` method to register and implement the transcript callback.
```swift
convoAIAPI.addHandler(handler: self)
```
5. **Implement UI rendering logic**
Implement the `ConversationalAIAPIEventHandler` protocol in your UI module, and use `onTranscriptUpdated` to handle and render transcript updates.
```swift
extension ChatViewController: ConversationalAIAPIEventHandler {
public func onTranscriptUpdated(agentUserId: String, transcript: Transcript) {
// Handle transcript data and update the UI here
}
}
```
6. **Add a conversational AI agent to the channel**
To [start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join), configure the following parameters in your `POST` request:
| Parameter | Description | Required |
| --------------------------------------- | -------------------------------------------------- | -------- |
| `advanced_features.enable_rtm: true` | Starts the Signaling service | Yes |
| `parameters.data_channel: "rtm"` | Enables Signaling as the data transmission channel | Yes |
| `parameters.enable_metrics: true` | Enables agent performance data collection | Optional |
| `parameters.enable_error_message: true` | Enables reporting of agent error events | Optional |
After a successful response, the agent joins the specified RTC channel and is ready to interact with the user.
7. **Unsubscribe from the channel**
After each agent session ends, unsubscribe from channel messages to release transcript-related resources.
```swift
convoAIAPI.unsubscribeMessage(channelName: channelName) { error in
if let error = error {
print("Unsubscription failed: \(error.message)")
} else {
print("Unsubscribed successfully")
}
}
```
8. **Release resources**
At the end of each call, use the `destroy` method to clean up the cache.
```swift
convoAIAPI.destroy()
```
1. **Integrate the toolkit**
Copy the [`conversational-ai-api`](https://github.com/AgoraIO-Community/Conversational-AI-Demo/tree/main/Web/Scenes/VoiceAgent/src/conversational-ai-api) file to your project and import the toolkit before calling its APIs. Refer to [Folder structure](#reference) to understand the role of each file.
2. **Create a toolkit instance**
Before joining an RTC channel, create Video SDK and Signaling engine instances and pass them into the toolkit instance.
```ts
// Initialize the component
ConversationalAIAPI.init({
rtcEngine,
rtmEngine,
/**
* Set the rendering mode for transcript. Available options:
* - ESubtitleHelperMode.WORD: render transcript word by word.
* - ESubtitleHelperMode.TEXT: render the full transcript at once.
*/
renderMode: ESubtitleHelperMode.WORD,
})
// Get the API instance (singleton)
const conversationalAIAPI = ConversationalAIAPI.getInstance()
```
3. **Set audio parameters**
In word-by-word rendering mode, you must receive audio timestamp metadata from RTC to synchronize subtitles with speech. Before creating the client object, configure the following parameter:
```ts
AgoraRTC.setParameter('ENABLE_AUDIO_PTS_METADATA', true)
const client = AgoraRTC.createClient({ mode: 'rtc', codec: 'vp8' })
```
4. **Subscribe to the channel**
Agent-related events are delivered through Signaling messages. Before starting an agent session, call `subscribeMessage` to receive these events:
```ts
conversationalAIAPI.subscribeMessage(channel_name)
```
5. **Receive transcript**
Register an event listener to receive transcript updates:
```tsx
import * as React from 'react'
import {
type IUserTranscription,
type IAgentTranscription,
type ISubtitleHelperItem,
EConversationalAIAPIEvents,
} from '@/conversational-ai-api/type'
import { ConversationalAIAPI } from '@/conversational-ai-api'
export const ChatHistory = () => {
const [chatHistory, setChatHistory] = React.useState<
ISubtitleHelperItem>[]
>([])
const conversationalAIAPI = ConversationalAIAPI.getInstance()
conversationalAIAPI.on(
EConversationalAIAPIEvents.TRANSCRIPT_UPDATED,
setChatHistory
)
return (
<>
{chatHistory.map((message) => (
{message.uid}: {message.text}
))}
>
)
}
```
6. **Add a conversational AI agent to the channel**
To [start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join), configure the following parameters in your `POST` request:
| Parameter | Description | Required |
| --------------------------------------- | -------------------------------------------------- | -------- |
| `advanced_features.enable_rtm: true` | Starts the Signaling service | Yes |
| `parameters.data_channel: "rtm"` | Enables Signaling as the data transmission channel | Yes |
| `parameters.enable_metrics: true` | Enables agent performance data collection | Optional |
| `parameters.enable_error_message: true` | Enables reporting of agent error events | Optional |
After a successful response, the agent joins the specified RTC channel and is ready to interact with the user.
7. **Unsubscribe from the channel**
After each agent session ends, unsubscribe from channel messages to release resources associated with callback events.
```ts
conversationalAIAPI.unsubscribeMessage(channel_name)
```
8. **Release resources**
At the end of each call, use the `destroy` method to clean up the cache.
```ts
conversationalAIAPI.destroy()
```
## Reference [#reference]
This section contains supporting information that completes the guidance on this page.
Android
iOS
Web
**Folder structure**
* `IConversationalAIAPI.kt`: API interface and related data structures and enumerations
* `ConversationalAIAPIImpl.kt`: ConversationalAI API main implementation logic
* `ConversationalAIUtils.kt`: tool functions and event callback management
* `subRender/`
* `v3/`: transcription module
* `TranscriptionController.kt`: transcription controller
* `MessageParser.kt`: message parser
**API reference**
* [`addHandler`](/en/api-reference/api-ref/conversational-ai/client-toolkit/android#addhandler)
* [`subscribeMessage`](/en/api-reference/api-ref/conversational-ai/client-toolkit/android#subscribemessage)
* [`unsubscribeMessage`](/en/api-reference/api-ref/conversational-ai/client-toolkit/android#unsubscribemessage)
* [`destroy`](/en/api-reference/api-ref/conversational-ai/client-toolkit/android#destroy)
* [`onTranscriptUpdated`](/en/api-reference/api-ref/conversational-ai/client-toolkit/android#ontranscriptupdated)
**Folder structure**
* `ConversationalAIAPI.swift`: API interface and related data structures and enumerations
* `ConversationalAIAPIImpl.swift`: ConversationalAI API main implementation logic
* `Transcription/`
* `TranscriptionController.swift`: transcription controller
**API reference**
* [`addHandler`](/en/api-reference/api-ref/conversational-ai/client-toolkit/ios#addhandler)
* [`subscribeMessage`](/en/api-reference/api-ref/conversational-ai/client-toolkit/ios#subscribemessage)
* [`unsubscribeMessage`](/en/api-reference/api-ref/conversational-ai/client-toolkit/ios#unsubscribemessage)
* [`destroy`](/en/api-reference/api-ref/conversational-ai/client-toolkit/ios#destroy)
* [`onTranscriptUpdated`](/en/api-reference/api-ref/conversational-ai/client-toolkit/ios#ontranscriptupdated)
**Folder structure**
* `index.ts`: API class
* `type.ts`: API interface and related data structures and enumerations
* `utils/`
* `index.ts`: API utility functions
* `events.ts`: event management class
* `sub-render.ts`: transcription module
**API reference**
* [`IConversationalAIAPIEventHandler interface`](/en/api-reference/api-ref/conversational-ai/client-toolkit/web#iconversationalaiapieventhandlers-interface)
* [`EConversationalAIAPIEvents`](/en/api-reference/api-ref/conversational-ai/client-toolkit/web#econversationalaiapievents)
* [`subscribeMessage`](/en/api-reference/api-ref/conversational-ai/client-toolkit/web#subscribemessage)
* [`unsubscribeMessage`](/en/api-reference/api-ref/conversational-ai/client-toolkit/web#unsubscribe)
* [`destroy`](/en/api-reference/api-ref/conversational-ai/client-toolkit/web#destroy)
# Convo AI Device Kit (/en/ai/device-kit)
Convo AI Device Kit R1 is a device-oriented product built on top of Agora's IoT stack and Conversational AI Engine. It packages hardware, connectivity, audio processing, and software resources into a faster path for smart device teams.
Use this product space when you need a hardware-first developer workflow instead of the general Conversational AI build path.
## Start here [#start-here]
* [Quickstart](start-here/quickstart)
## Build [#build]
* [Run the R1 demo](build/run-the-r1-demo)
* [Run the demo server](build/run-the-demo-server)
* [Configure device network](build/configure-device-network)
* [Build and flash firmware](build/build-and-flash-firmware)
## Understand the system [#understand-the-system]
* [Architecture overview](build/architecture-overview)
* [Specifications and compatibility](build/specifications-and-compatibility)
## Plan rollout [#plan-rollout]
* [Enable services](reference/enable-services)
* [Pricing](reference/pricing)
* [Release notes](reference/release-notes)
# Integrate with MCP (/en/ai/get-started/mcp-integrate)
## Connect to the Agora Doc MCP Server [#connect-to-the-agora-doc-mcp-server]
Choose one of the following ways to connect to the MCP server:
#### Trae [#trae]
**Manual configuration:**
Add the following configuration manually in Trae under Settings > MCP:
```json title=
{
"mcpServers": {
"shengwang-docs": {
"type": "http",
"url": "https://mcp.agora.io"
}
}
}
```
#### Claude Code [#claude-code]
**Add with the `CLI`:**
```bash
claude mcp add --transport http shengwang-docs https://mcp.agora.io
```
**Or configure it manually:**
Create or edit `.mcp.json` in the project root, then add the following section:
```json
{
"mcpServers": {
"shengwang-docs": {
"type": "http",
"url": "https://mcp.agora.io"
}
}
}
```
#### Codex [#codex]
**Add with the `CLI`:**
```bash
codex mcp add shengwang-docs --url https://mcp.agora.io
```
**Or configure it manually:**
Create or edit `.codex/config.toml` in the project root, then add the following section:
```text
[mcp_servers.shengwang-docs]
url = "https://mcp.agora.io"
```
#### Cursor [#cursor]
**Manual configuration:**
Create or edit `.cursor/mcp.json` in the project root, then add the following section:
```json
{
"mcpServers": {
"shengwang-docs": {
"url": "https://mcp.agora.io"
}
}
}
```
#### Kiro [#kiro]
**Manual configuration:**
Create or edit `.kiro/settings/mcp.json` in the project root, then add the following section:
```json
{
"mcpServers": {
"shengwang-docs": {
"url": "https://mcp.agora.io"
}
}
}
```
#### VS Code & Copilot [#vs-code--copilot]
**Add with the `CLI`:**
```bash
code --add-mcp '{"name":"shengwang-docs","type":"http","url":"https://mcp.agora.io"}'
```
**Or configure it manually:**
Create or edit `.vscode/mcp.json` in the root directory, then add the following section:
```json
{
"servers": {
"shengwang-docs": {
"type": "http",
"url": "https://mcp.agora.io"
}
}
}
```
## MCP tools [#mcp-tools]
* `search-docs`: Search for relevant documentation.
* `list-docs`: Browse documentation categories and document lists.
* `get-doc-content`: Read the full content of a specified document.
## Use MCP tools [#use-mcp-tools]
**Automatically called by the coding agent**
In Agent Mode, you can describe your requirement directly and let the coding agent decide when to call MCP tools.
For example:
```text
Search the relevant Agora documentation, read the document content, and tell me how to build the simplest possible 1v1 real-time audio and video call.
```
**Specify tool calls in the prompt**
You can also specify which MCP tools to use in the prompt through the tool-calling syntax supported by your AI IDE.
For example:
```text
#search-docs #get-doc-content Use search-docs to find relevant Agora documentation, then use get-doc-content to read the full content of the matched document and tell me how to build an interactive experience with an AI agent.
```
# Voice agent quickstart (/en/ai/get-started/quickstart)
Set up a working voice agent in under five minutes. This page walks you through installing the Agora skills to give your AI coding assistant the official quickstarts and Agora CLI workflows. You then use the CLI to log in, clone the official starter, and run it locally. You can follow the CLI steps yourself or paste the sample prompt and let your assistant handle setup for you.
### Install Agora skills [#install-agora-skills]
Agora skills teaches your AI coding assistant how to work with Conversational AI projects using official starter repos and the Agora CLI, including signing in, project binding, generating environment files, and running diagnostics. Install the CLI in the next section, or ask your assistant to run the installer for you.
```bash
npx skills add https://github.com/agoraio/skills --skill agora
```
Paste the following prompt into your assistant's chat. You can replace Python with TypeScript or Go depending on your language preference:
```text
Set up and run the Agora Conversational AI Python starter project locally.
```
Your assistant installs the CLI, signs you in, scaffolds the official starter, and guides you through the remaining steps. Follow the manual CLI steps below if you prefer to run each command yourself.
### Install the Agora CLI [#install-the-agora-cli]
The Agora CLI is a native Go binary available at [AgoraIO-Community/cli](https://github.com/AgoraIO-Community/cli).
macOS and Linux
Windows (PowerShell)
```bash
curl -fsSL https://agoraio.github.io/cli/install.sh | sh
agora --help
```
If the `agora` command is not found after installation, re-run with `--add-to-path` or
manually add the install directory to your shell profile.
```powershell
irm https://agoraio.github.io/cli/install.ps1 | iex
agora --help
```
If your execution policy blocks inline scripts, download `install.ps1` and run:
```powershell
powershell -ExecutionPolicy Bypass -File .\install.ps1
```
## Sign in, scaffold, and run [#sign-in-scaffold-and-run]
Sign in with the Agora CLI, clone the starter project, and configure it for your chosen language.
1. Sign in to Agora Console.
```bash
agora login
```
2. Use `agora init` to clone the official starter for your chosen template, bind it to your
Agora project, and write the runtime-specific environment file.
Python
TypeScript
Go
```bash
agora init my-python-demo --template python
cd my-python-demo
bun install
bun run dev
```
```bash
agora init my-nextjs-demo --template nextjs
cd my-nextjs-demo
pnpm install
pnpm dev
```
```bash
agora init my-go-demo --template go
cd my-go-demo
make setup
make dev
```
3. Open `http://localhost:3000` and click **Start conversation**.
If the agent does not join or transcripts do not appear, run `agora project doctor` to
check credential validity, feature enablement, and network reachability.
## Next steps [#next-steps]
Explore the following topics:
* [Start and stop an agent](../build/start-stop-agent): Create and end a voice agent session with the REST API.
* [Use managed mode](../build/custom-model-integration/managed-mode): Start with Agora-managed ASR, LLM, and TTS providers.
* [Optimize conversation latency](../best-practices/optimize-latency): Tune ASR, LLM, TTS, and deployment choices.
* [Optimize audio](../best-practices/audio-setup): Apply recommended client audio settings.
* [REST API reference](/en/api-reference/api-ref/conversational-ai): Review exact request and response shapes.
* Explore the SDK API references for [TypeScript](/en/api-reference/api-ref/server-sdk/typescript), [Python](/en/api-reference/api-ref/server-sdk/python), and [Go](/en/api-reference/api-ref/server-sdk/go).
# Integrate with Skills (/en/ai/get-started/skills-integrate)
## Install Agora skills [#install-agora-skills]
Choose one of the following ways to install the skills:
#### Skills CLI [#skills-cli]
Install with the `CLI`:
```bash
npx skills add AgoraIO/skills
```
This is the most direct installation method. After the installation finishes, restart the session or refresh the skills list according to the instructions for your coding agent.
#### Claude Code Plugin Marketplace [#claude-code-plugin-marketplace]
Run the following command in `Claude Code`:
```bash
plugin marketplace add AgoraIO/skills
```
#### OpenClaw [#openclaw]
Install through `ClawHub`:
```bash
clawhub install voice-ai-integration
clawhub update voice-ai-integration
```
Use `install` the first time and `update` for later upgrades.
## Repository [#repository]
* [Agora skills](https://github.com/AgoraIO/skills)
## Best practices [#best-practices]
After configuring the skills, you can directly describe tasks such as integration, code generation, or troubleshooting in your coding agent. The skills help the agent choose the right integration workflow and generation rules, and supplement responses with the latest Agora documentation, making generated code, configuration guidance, and issue diagnosis more accurate.
* Clearly specify the Agora product, target platform, development language, and desired feature in your prompt.
* Provide as much context as possible, such as your current tech stack, existing code, expected interaction flow, whether you need a token, and whether you need server-side examples. This helps the AI produce solutions that are easier to use directly.
* When troubleshooting, include the observed issue, reproduction steps, logs, error codes, screenshots, or call stack details whenever possible so the coding agent can narrow down the cause faster.
* For complex tasks, prefer an AI IDE with stronger model support and more complete tooling for more stable code generation and problem analysis.
## Prompt example [#prompt-example]
After configuring the skills, you can send a prompt like the following to your coding agent to generate an AI voice conversation demo with real-time captions:
```markdown
Build me an AI voice conversation demo. After the user opens the web page and clicks "Start conversation", they should be able to talk with the AI by voice, and the page should display real-time captions.
## Tech stack
- Frontend: Next.js + React + TypeScript + Tailwind CSS
- Backend: FastAPI (Python)
## Requirements
1. Start and end the voice conversation with one click
2. Show real-time captions for both the user and the AI
3. Mute and unmute the microphone
4. Display the AI status (listening, thinking, speaking)
5. Include a system log panel
```
# Enable service (/en/ai/reference/enable-conversational-ai)
Before you build with Conversational AI, enable the service for your Agora project in the Agora Console.
## Basic flow [#basic-flow]
1. Sign in to the [Agora Console](https://console.agora.io).
2. Open **Projects** and select the target project.
3. Under **Conversational AI**, enable the service for the project.
4. Generate the credentials you need for REST API or SDK access.
# Notification event types (/en/ai/reference/event-types)
After enabling Agora message notifications, the Agora notification server sends channel event notifications to your server through HTTPS POST requests. The data format is `JSON`, the character encoding is `UTF-8`, and the signature algorithm can be either `HMAC/SHA1` or `HMAC/SHA256`.
This page explains the types of events returned in channel event callbacks and their meanings.
If you are starting from a product goal instead of an event number, begin with [Monitor agent status, errors, and performance](../build/handle-runtime-events/monitor-agent-runtime).
For webhook setup, request format, and signature verification, see [Webhooks](../build/handle-runtime-events/webhooks).
## Choose event types by goal [#choose-event-types-by-goal]
| Goal | Use these event types | What they help you answer |
| --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Confirm that a session started or ended | [`101 agent joined`](#101-agent-joined), [`102 agent left`](#102-agent-left) | Did the agent enter the channel? How did the session end? |
| Retrieve conversation history after a session | [`103 agent history`](#103-agent-history) | What was said during the session? See [Retrieve conversation history after a session ends](../build/handle-runtime-events/retrieve-session-history). |
| Investigate failures and alert on production issues | [`110 agent error`](#110-agent-error), [`102 agent left`](#102-agent-left) | Which module failed? Did the session terminate because of that failure? See [Debug agent failures with runtime events](../build/handle-runtime-events/debug-agent-failures). |
| Measure latency and runtime health | [`111 agent metrics`](#111-agent-metrics) | How long do LLM and TTS stages take? Are there regressions? |
| Track telephony call progress | [`201 inbound call state`](#201-inbound-call-state), [`202 outbound call state`](#202-outbound-call-state) | Where is the SIP call in its lifecycle? |
## Request header [#request-header]
The message notification callback header contains the following fields:
Application/json
The signature value generated by Agora using the customer key and `HMAC/SHA1` algorithm. Use the customer key and `HMAC/SHA1` algorithm to verify the signature value. See Verify the signature for details.
The signature value generated by Agora using the customer key and `HMAC/SHA256` algorithm. Use the customer key and `HMAC/SHA256` algorithm to verify the signature value. See Verify the signature for details.
## Request body [#request-body]
The message notification callback request body contains the following fields:
Notification ID. Identifies an event notification from the Agora server.
Business ID. A value of `17` indicates a Conversational AI Engine notification.
The event type of the notification. See [Event types](#event-types) for details.
The Unix timestamp (ms) indicating when the Agora message server sent the event notification to your server. This value is updated when the notification is retried.
The session ID.
The specific content of the notification event. `payload` varies depending on the event type. For details, see [Event types](#event-types).
Following is an example of a message notification callback request body:
```json
{
"sid": "C866467GVJJ54687",
"noticeId": "2000001428:4330:107",
"productId": 17,
"eventType": 101,
"notifyMs": 1611566412672,
"payload": {}
}
```
## Event types [#event-types]
The Agora message notification service notifies the following Conversational AI Engine events:
| Event Type | Event name | Event description |
| :------------------------------ | :------------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [101](#101-agent-joined) | agent joined | The agent joins the channel. |
| [102](#102-agent-left) | agent left | The agent leaves the channel. |
| [103](#103-agent-history) | agent history | After an agent stops, this event notifies the stored history, which includes messages exchanged between the user and the agent and timestamps indicating when the agent was created and stopped. The maximum number of entries is determined by the `llm.max_history` parameter you can set when starting the agent. The default value is `32`. |
| [104](#104-agent-expire) | agent expire | The agent's RTC token is about to expire. |
| [110](#110-agent-error) | agent error | Agent error. |
| [111](#111-agent-metrics) | agent metrics | The performance metrics of an agent. |
| [112](#112-turns-finished) | turns finished | Batch callback of dialogue round data after the session ends. |
| [201](#201-inbound-call-state) | inbound call state | Changes in the status of an incoming call. |
| [202](#202-outbound-call-state) | outbound call state | Changes in the status of an outgoing call. |
### 101 agent joined [#101-agent-joined]
An `eventType` of `101` indicates that an agent has joined a channel. The `payload` contains the following fields:
Unique identifier of the agent.
The agent name provided when calling [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join). Unique within a channel.
Timestamp indicating when the agent was created.
The name of the channel the agent was in.
Custom labels in key-value pair format. Contains the same labels that were set when starting the agent.
#### Payload example [#payload-example]
```json
{
"agent_id": "1NT29X10YHxxxxxWJOXLYHNYB",
"name": "my-agent",
"start_ts": 1737111452,
"channel": "xxxxx",
"labels": {
"campaign_id": "test_campaign",
"customer_group": "vip"
}
}
```
### 102 agent left [#102-agent-left]
An `eventType` of `102` indicates that an agent has left a channel. The `payload` contains the following fields:
Unique identifier of the agent.
The agent name provided when calling [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join). Unique within a channel.
Timestamp indicating when the agent was created.
Timestamp indicating when the agent left the channel.
The name of the channel the agent was in.
Agent status.
The reason why the agent left the channel.
Custom labels in key-value pair format. Contains the same labels that were set when starting the agent.
#### Payload examples [#payload-examples]
Following are some examples of the `payload` when an agent leaves a channel for different reasons.
Agent manually stopped
Agent idle timeout
Agent error
RTC connection error
Task lifetime limit exceeded
```json
{
"agent_id": "1NT29X10YHxxxxxWJOXLYHNYB",
"name": "my-agent",
"start_ts": 1737111452,
"stop_ts": 1737111455,
"channel": "xxxxx",
"status": "STOPPED",
"message": "OK",
"labels": {
"campaign_id": "test_campaign",
"customer_group": "vip"
}
}
```
```json
{
"agent_id": "1NT29X10YHxxxxxWJOXLYHNYB",
"name": "my-agent",
"start_ts": 1737111452,
"stop_ts": 1737111455,
"channel": "xxxxx",
"status": "STOPPED",
"message": "Idle for too long",
"labels": {
"campaign_id": "test_campaign",
"customer_group": "vip"
}
}
```
```json
{
"agent_id": "1NT29X10YHxxxxxWJOXLYHNYB",
"name": "my-agent",
"start_ts": 1737111452,
"stop_ts": 1737111455,
"channel": "xxxxx",
"status": "FAILED",
"message": "Connecting for too long",
"labels": {
"campaign_id": "test_campaign",
"customer_group": "vip"
}
}
```
```json
{
"agent_id": "1NT29X10YHxxxxxWJOXLYHNYB",
"name": "my-agent",
"start_ts": 1737111452,
"stop_ts": 1737111455,
"channel": "xxxxx",
"status": "FAILED",
"message": "RTC connection error",
"labels": {
"campaign_id": "test_campaign",
"customer_group": "vip"
}
}
```
```json
{
"agent_id": "1NT29X10YHxxxxxWJOXLYHNYB",
"name": "my-agent",
"start_ts": 1737111452,
"stop_ts": 1737111455,
"channel": "xxxxx",
"status": "STOPPED",
"message": "Task lifetime limit exceeded",
"labels": {
"campaign_id": "test_campaign",
"customer_group": "vip"
}
}
```
### 103 agent history [#103-agent-history]
An `eventType` of `103` notifies the history of a user and agent dialogue. The notification `payload` contains the following fields:
Unique identifier of the agent.
The agent name provided when calling [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join). Unique within a channel.
The name of the channel the agent was in.
The timestamp indicating when the agent started and joined the channel.
The timestamp indicating when the agent stopped and left the channel.
Agent history.
The message sender.
* `user`: User
* `assistant`: AI agent
Message content.
Unix timestamp in milliseconds indicating when the user started speaking or the agent started TTS playback. Only returned when `llm.vendor` is `custom`.
Unix timestamp in milliseconds indicating when the user stopped speaking, or when TTS playback completed or was interrupted. Only returned when `llm.vendor` is `custom`.
The total delay in milliseconds introduced by audio processing algorithms, including noise reduction, background voice suppression, and voiceprint locking, after audio is captured from the user's microphone. Use this value to align timestamps with cloud recording audio.
Only returned when:
* `llm.vendor` is `custom`
* `contents[].role` is `user`
* Actual voice input is present
Custom labels in key-value pair format. Contains the same labels that were set when starting the agent.
`speech_start_ms`, `speech_end_ms`, and `speech_algorithmic_delay` are returned only in `103 agent history` events for sessions that use `llm.vendor` set to `custom`.
#### Payload examples [#payload-examples-1]
Basic
Custom LLM
```json
{
"agent_id": "xxxx",
"name": "my-agent",
"channel": "xxxx",
"start_ts": 123,
"stop_ts": 123,
"contents": [
{
"role": "user",
"content": "hello."
},
{
"role": "assistant",
"content": "hi, how can I help you?"
}
],
"labels": {
"campaign_id": "test_campaign",
"customer_group": "vip"
}
}
```
```json
{
"agent_id": "xxxx",
"name": "my-agent",
"channel": "xxxx",
"start_ts": 123,
"stop_ts": 123,
"contents": [
{
"role": "user",
"content": "Hello, I need help with my recent order.",
"speech_start_ms": 1715000001200,
"speech_end_ms": 1715000003400,
"speech_algorithmic_delay": 120
},
{
"role": "assistant",
"content": "Sure, could you please provide your order number?",
"speech_start_ms": 1715000004100,
"speech_end_ms": 1715000005000
},
{
"role": "user",
"content": "I'd like to check the status of my package, tracking number 12345.",
"speech_start_ms": 1715000005200,
"speech_end_ms": 1715000007600,
"speech_algorithmic_delay": 120
},
{
"role": "assistant",
"content": "Your package with tracking number 12345 is currently out for delivery.",
"speech_start_ms": 1715000009500,
"speech_end_ms": 1715000013200
},
{
"role": "user",
"content": "[think API injected] User level: VIP"
}
],
"labels": {
"campaign_id": "test_campaign",
"customer_group": "vip"
}
}
```
### 104 agent expire [#104-agent-expire]
An `eventType` of `104` indicates that the agent's RTC token is about to expire. The `payload` contains the following fields:
Unique identifier of the agent.
The agent name provided when calling [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join). Unique within a channel.
Timestamp indicating when the agent was created.
The name of the channel the agent is in.
Token expiry warning message. Fixed value: `Task exceeded maximum lifetime`.
Info
This event is a warning only. The agent does not exit the channel or interrupt the session when this event is triggered. Upon receiving this event, call the [Update agent](/en/api-reference/api-ref/conversational-ai/update) API with a new `properties.token` to refresh the token before it expires.
#### Payload example [#payload-example-1]
```json
{
"agent_id": "1NT29X10YHxxxxxWJOXLYHNYB",
"name": "my-agent",
"start_ts": 1737111452,
"channel": "xxxxx",
"message": "Task exceeded maximum lifetime"
}
```
### 110 agent error [#110-agent-error]
An `eventType` of `110` indicates that an agent has encountered an error. The `payload` contains the following fields:
Unique identifier of the agent.
The agent name provided when calling [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join). Unique within a channel.
Timestamp indicating when the agent was created.
The name of the channel the agent was in.
Transcript conversation turn. For details, see [Display live transcripts](../build/transcripts#reference).
An array of error messages. Each object contains the following fields:
The module where the error occurred.
Transcript dialogue turn.
Error code. Refer to the error code document of the vendor corresponding to the error module for detailed information.
The error message.
Custom labels in key-value pair format. Contains the same labels that were set when starting the agent.
#### Payload examples [#payload-examples-2]
LLM module error
Greeting audio URL error
{/* SIP module error */}
```json
{
"agent_id": "1NT29X10YHxxxxxWJOXLYHNYB",
"name": "my-agent",
"start_ts": 1737111452,
"channel": "xxxxx",
"turn_id": 2,
"errors": [{"module": "llm", "turn_id":2, "code":503, "message": "Insufficient balance."}],
"labels": {
"campaign_id": "test_campaign",
"customer_group": "vip"
}
}
```
When the greeting audio file fails to download, times out, is in an unsupported format, or fails to decode, the system reports the error via a `110 agent error` event and automatically falls back to TTS synthesis using `greeting_message`. In these errors, `errors[].module` is `agent`.
```json
{
"agent_id": "1NT29X10YHxxxxxWJOXLYHNYB",
"name": "outbound_voice_agent",
"start_ts": 1737111452,
"channel": "outbound_call_channel_001",
"turn_id": 0,
"errors": [
{
"module": "agent",
"turn_id": 0,
"code": 4001,
"message": "Greeting audio URL unreachable, fallback to TTS. audio_url: https://cdn.example.com/audio/welcome.mp3"
}
],
"labels": {}
}
```
{/*
```json
{
"agent_id": "1NT29X10YHxxxxxWJOXLYHNYB",
"name": "my-agent",
"start_ts": 1737111452,
"channel": "xxxxx",
"turn_id": "",
"errors": [{"module": "sip", "turn_id": "", "code":50, "message": "Insufficient balance."}],
"labels": {
"campaign_id": "test_campaign",
"customer_group": "vip"
}
}
```
*/}
Info
If `parameters.enable_error_message` is set to `true` when starting the agent, greeting audio URL errors are also delivered to the client via the `onMessageError` RTM callback, in addition to the server-side webhook event.
### 111 agent metrics [#111-agent-metrics]
An `eventType` of `111` notifies the performance metrics of an agent. The `payload` contains the following fields:
Unique identifier of the agent.
The agent name provided when calling [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join). Unique within a channel.
Timestamp indicating when the agent was created and joined the channel.
Timestamp indicating when the agent stopped and left the channel.
The name of the channel where the agent is located.
An array of performance metrics. Each object contains the following fields:
Dialogue round ID.
The ttlw (Time To Last Word) metric of the ASR module. Represents the delay (in milliseconds) from when the user finishes speaking until the last word is output by the ASR.
The ttfb (Time To First Byte) metric for the LLM module. Represents the latency (in milliseconds) from the start of an LLM request to the receipt of the first byte of the response.
The ttfs (Time To First Sentence) metric for the LLM module. Represents the time (in milliseconds) elapsed from the start of an LLM request to the receipt of the first complete sentence response.
The ttfb (Time To First Byte) metric for the TTS module. Represents the latency (in milliseconds) from the start of a TTS request to the receipt of the first byte of the response.
Custom labels in key-value pair format. Contains the same labels that were set when starting the agent.
#### Payload example [#payload-example-2]
```json
{
"agent_id": "A42AC47Hxxxxxxxx4PK27ND25E",
"name": "my-agent",
"start_ts": 1000,
"stop_ts": 1672531200,
"channel": "test-channel",
"metrics": [
{
"turn_id": 1,
"asr_ttlw": 503,
"llm_ttfb": 1104,
"tts_ttfb": 85
},
{
"turn_id": 2,
"asr_ttlw": 2385,
"llm_ttfb": 980,
"tts_ttfb": 78
}
],
"labels": {
"campaign_id": "test_campaign",
"customer_group": "vip"
}
}
```
### 112 turns finished [#112-turns-finished]
An `eventType` of `112` indicates a batch callback of conversation turn data after the session ends. This event returns the conversation turns for the current session in a single callback, making it convenient for post-session analysis, archiving, or offline processing.
When a session has more than 200 turns, the callback data may be truncated. Use the `is_truncated` field to check whether the current callback contains the complete turn data.
The `payload` contains the following fields:
Unique identifier of the agent.
The agent name provided when calling [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join). Unique within a channel.
The name of the channel where the agent is located.
Timestamp indicating when the agent was created and joined the channel.
Timestamp indicating when the agent stopped and left the channel.
The total number of conversation turns in the session.
Whether the turn data in the current callback is truncated. A value of `true` indicates that the current callback did not return all turns.
Custom labels in key-value pair format. Contains the same labels that were set when starting the agent. Only returned if labels were passed in when creating the agent.
A list of conversation turns for the session.
Unique identifier of the agent.
The name of the channel where the agent is located.
The sequential index of the turn within the session. Starts at `1`.
Details about the start of the turn.
The Unix timestamp in milliseconds (UTC time) when the turn started.
The type of event that initiated the turn.
Additional context about the turn start event. Included fields depend on the value of the `type` field.
The duration of the user's voice input in milliseconds. Included only when `type` is `voice_input`.
The minimum voice duration in milliseconds required to trigger an interruption. Included only when `type` is `voice_input`.
Details about the end of the turn.
The Unix timestamp in milliseconds (UTC time) when the turn ended.
The type of event that ended the turn.
Additional context about the turn end event.
Latency metrics for the turn.
The end-to-end latency in milliseconds for the turn. Represents the time from when the user finishes speaking to when the agent starts speaking.
A breakdown of latency by segment.
Algorithm processing delay in milliseconds.
The ASR Time To Last Word (TTLW) in milliseconds. Represents the delay from when the user finishes speaking to when the ASR module outputs the last word.
The LLM Time To First Token (TTFT) in milliseconds. Represents the delay from when the LLM receives the request to when it outputs the first token.
The LLM First Token To First Sentence (FTFS) in milliseconds. Represents the delay from when the LLM outputs the first token to when it outputs the first complete sentence.
The TTS Time To First Byte (TTFB) in milliseconds. Represents the delay from when the TTS module receives a text request to when it outputs the first audio byte.
Network transmission delay in milliseconds. Not returned when the user is connected using the RTC Web SDK.
#### Payload example [#payload-example-3]
```json
{
"agent_id": "xxxx",
"name": "support_agent_001",
"channel": "xxxx",
"start_ts": 1715000000000,
"stop_ts": 1715000015000,
"total_turn_count": 250,
"is_truncated": true,
"labels": {
"campaign_id": "test_campaign",
"customer_group": "vip"
},
"turns": [
{
"agent_id": "xxxx",
"channel": "xxxx",
"turn_id": 1,
"start": {
"start_at": 1774579820147,
"type": "voice_input"
},
"end": {
"end_at": 1774579822412,
"type": "ok"
},
"metrics": {
"e2e_latency_ms": 1500,
"segmented_latency_ms": {
"algorithm_processing": 300,
"asr_ttlw": 200,
"llm_ttft": 600,
"tts_ttfb": 200,
"transport": 300
}
}
}
]
}
```
### 201 inbound call state [#201-inbound-call-state]
An `eventType` of `201` notifies changes in the status of an incoming call. The `payload` contains the following fields:
Unique identifier of the agent.
The agent name provided when calling [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join). Unique within a channel.
The name of the channel where the agent is located.
Incoming call status:
* `START`: Call received
* `ANSWERED`: Call answered
* `TRANSFERED`: Transfer to human operator
* `HANGUP`: Call hung up
Timestamp (in milliseconds) of the state change.
Custom labels in key-value pair format. Contains the same labels that were set when starting the agent.
#### Payload example [#payload-example-4]
```json
{
"agent_id": "1NT29X10YHxxxxxWJOXLYHNYB",
"name": "my-agent",
"channel": "xxxxx",
"state": "START",
"report_ms": 1737111452000,
"labels": {
"campaign_id": "test_campaign",
"customer_group": "vip"
}
}
```
### 202 outbound call state [#202-outbound-call-state]
An `eventType` of `202` notifies changes in the status of an outgoing call. The `payload` contains the following fields:
Unique identifier of the agent.
The agent name provided when calling [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join). Unique within a channel.
The name of the channel where the agent is located.
Outgoing call status:
* `START`: Call begins
* `CALLING`: Making a phone call
* `RINGING`: The telephone is ringing
* `ANSWERED`: The call was connected
* `HANGUP`: Call hung up
Timestamp (in milliseconds) of the state change.
Custom labels in key-value pair format. Contains the same labels that were set when starting the agent.
#### Payload example [#payload-example-5]
```json
{
"agent_id": "1NT29X10YHxxxxxWJOXLYHNYB",
"name": "my-agent",
"channel": "xxxxx",
"state": "CALLING",
"report_ms": 1737111452000,
"labels": {
"campaign_id": "test_campaign",
"customer_group": "vip"
}
}
```
# OpenAI Realtime integration (/en/ai/reference/openai-realtime-integration)
This guide explains how to combine Agora's realtime audio transport with the OpenAI Realtime API.
Use this integration when you want Agora to handle the live audio session and network transport, while OpenAI Realtime handles the model-side conversational behavior.
This is not the same as building on Agora's managed Conversational AI Engine. If you want Agora to manage the voice-agent runtime, use the main Conversational AI build path instead.
## When to use this path [#when-to-use-this-path]
* You already know you want to build around the OpenAI Realtime API.
* You need Agora RTC or Video SDK transport in front of that model stack.
* You are building a browser demo, device-oriented flow, or backend-mediated prototype around OpenAI Realtime.
## When not to use this path [#when-not-to-use-this-path]
* You want the default Agora-managed voice-agent stack.
* You are looking for the main onboarding path for Conversational AI Engine.
* You want Agent Studio or the Device Kit route instead of a custom integration.
## Core pieces [#core-pieces]
* **Agora App ID**: Identifies your Agora project.
* **App certificate and token model**: Used for secure join flows where needed.
* **Channel and user ID**: Define the live session and participants.
* **SD-RTN**: Provides Agora's low-latency transport layer.
* **OpenAI Realtime API**: Provides the model-side realtime conversation flow.
## Recommended quickstart flow [#recommended-quickstart-flow]
1. Prepare your Agora account and project.
2. Decide how your client joins the Agora session and how the OpenAI-side session is coordinated.
3. Configure the OpenAI Realtime model and audio flow.
4. Run the round-trip voice path locally and validate latency, auth, and media behavior.
## Security and network checks [#security-and-network-checks]
* Keep Agora credentials and provider API keys on the server side.
* Separate local, staging, and production credentials.
* Review firewall requirements before testing in restricted networks.
* Re-test the full join and media path after changing transport or security settings.
## Related pages [#related-pages]
* [Conversational AI quickstart](/en/ai/get-started/quickstart)
* [MCP integration](/en/ai/get-started/mcp-integrate)
* [Agora skills](/en/ai/get-started/skills-integrate)
* [Security and privacy](/en/introduction/security-privacy)
* [Firewall requirements](/en/introduction/firewall)
* [Glossary](/en/introduction/glossary)
# Pricing (/en/ai/reference/pricing)
This page describes how Agora calculates and bills for Conversational AI Engine usage.
When you use Conversational AI Engine in your project, Agora charges a monthly fee based on your usage across all projects under your developer account. At the end of each month, the free quota is subtracted from your total usage, and the remaining minutes are multiplied by the unit price to calculate your bill, rounded to two decimal places. For general billing information, see [Billing](../../introduction/billing).
Info
If you have signed a sales contract with Agora, your billing follows the terms in your contract.
## Unit price [#unit-price]
Creating a Conversational AI Engine instance using the REST API and joining a channel incurs an audio task fee at the following rate:
| Usage Type | Pricing (USD / minute) | Free Minutes |
| ----------------------------------- | ---------------------: | -------------------------- |
| Conversational AI Engine Audio Task | 0.10\* | First 300 minutes are free |
\* The unit price includes usage of selected ASR, LLM, and TTS models. You are charged the same price even if you bring your own key (BYOK).
Usage of ASR, LLM, and TTS providers is included in the unit price when using an Agora managed key. Agora provides and manages the API keys for the following providers:
* **ASR**
* ARES
* Deepgram nova-2
* Deepgram nova-3
* **LLM**
* OpenAI GPT-4o-mini
* OpenAI GPT-4.1-mini
* OpenAI GPT-5-nano
* OpenAI GPT-5-mini
* **TTS**
* MiniMax 2.6 Turbo
* MiniMax 2.8 Turbo
* OpenAI TTS-1
## Examples [#examples]
The following examples demonstrate how billing is calculated for different Conversational AI Engine configurations.
### Using ASR, LLM, and TTS managed by Agora [#using-asr-llm-and-tts-managed-by-agora]
User A joins a channel and starts a voice conversation with an instance created by Conversational AI Engine. The interaction lasts for 10 minutes. User A and the Conversational AI Engine instance exit the channel at the same time. Agora calculates the cost for this session as follows:
| Usage Type | Duration (minutes) | Unit Price | Service Cost (USD) |
| ---------------------------------------------------------- | -----------------: | ---------: | -----------------: |
| User A: Audio RTC | 10 | 0.00099 | 0.0099 |
| Conversational AI Engine Audio Task | 10 | 0.10 | 1.00 |
| ASR: ARES, LLM: OpenAI GPT-4o-mini, TTS: MiniMax 2.8 Turbo | 10 | 0.00 | 0.00 |
Total cost: 1.0099 USD
# Release notes (/en/ai/reference/release-notes)
This document tracks notable changes and improvements to the Conversational AI Engine.
## Releases [#releases]
### v2.9 [#v29]
Released on July 1, 2026.
#### New features [#new-features]
Included in this release:
* **Manual turn control**
You can now explicitly control when a user's speech starts and ends by sending manual Start of Speech (SoS) and End of Speech (EoS) requests over Signaling (RTM), instead of relying on automatic detection. This supports scenarios with strict turn-boundary requirements, such as AI interviews, interactive quizzes, or push-to-talk. Set [`start_of_speech.mode`](/en/api-reference/api-ref/conversational-ai/join#properties-turn-detection-config-start-of-speech-mode) and [`end_of_speech.mode`](/en/api-reference/api-ref/conversational-ai/join#properties-turn-detection-config-end-of-speech-mode) to `manual` when starting the agent. For details, see [Manually control start and end of speech](/en/ai/best-practices/manual-turn-control).
* **Agent state notifications over RTM Message**
You can now track agent state changes in real time using Signaling (RTM) Message events. The agent pushes `state.listening`, `state.thinking`, and `state.speaking` events as individual RTM messages, making it easier to record state change timestamps, process events sequentially, or consume state events alongside other RTM Message events. For details, see [Get agent state](/en/ai/best-practices/get-agent-state).
* **Pre-recorded greeting audio**
You can now play a pre-recorded audio file as the agent's greeting instead of synthesizing it with TTS. Set [`properties.llm.greeting_audio_url`](/en/api-reference/api-ref/conversational-ai/join#properties-llm-greeting-audio-url) to the URL of an `mp3`, `wav`, or `pcm` file when starting the agent. If the audio fails to download, decode, or is in an unsupported format, the agent automatically falls back to TTS synthesis using `greeting_message`.
* **Managed mode**
You can now use supported ASR, LLM, and TTS providers with Agora-managed credentials, without supplying your own API keys. Set [`credential_mode`](/en/api-reference/api-ref/conversational-ai/join#properties-asr-credential-mode) to `"managed"` within the `asr`, `llm`, or `tts` block when starting an agent. Managed mode replaces the deprecated `preset` parameter. For details, see [Use managed mode](/en/ai/build/custom-model-integration/managed-mode).
#### Improvements [#improvements]
This release includes the following enhancements.
* **Speech captured during an uninterruptible greeting**
When [`properties.llm.greeting_configs.interruptable`](/en/api-reference/api-ref/conversational-ai/join#properties-llm-greeting-configs-interruptable) is set to `false`, the agent now silently collects the user's ASR results while the greeting plays. After the greeting ends, the system concatenates the collected speech segments into a single user message and sends it to the LLM for a unified response, instead of discarding speech that occurs during playback.
#### API changes [#api-changes]
This release introduces the following changes to the RESTful API.
* Changes to [**Start a conversational AI agent**](/en/api-reference/api-ref/conversational-ai/join)
* **New parameters**
* [`properties.llm.greeting_audio_url`](/en/api-reference/api-ref/conversational-ai/join#properties-llm-greeting-audio-url): The URL of a pre-recorded audio file (`mp3`, `wav`, or `pcm`) to play as the agent's greeting instead of synthesizing it with TTS.
* [`properties.llm.greeting_configs.audio_download_timeout_ms`](/en/api-reference/api-ref/conversational-ai/join#properties-llm-greeting-configs-audio-download-timeout-ms): The timeout in milliseconds for downloading the greeting audio file. Defaults to `1000`.
* [`properties.llm.greeting_configs.audio_pcm_sample_rate`](/en/api-reference/api-ref/conversational-ai/join#properties-llm-greeting-configs-audio-pcm-sample-rate): The sample rate in Hz of the greeting audio file when the format is PCM. Defaults to `16000`.
* [`properties.asr.credential_mode`](/en/api-reference/api-ref/conversational-ai/join#properties-asr-credential-mode), [`properties.llm.credential_mode`](/en/api-reference/api-ref/conversational-ai/join#properties-llm-credential-mode), and [`properties.tts.credential_mode`](/en/api-reference/api-ref/conversational-ai/join#properties-tts-credential-mode): Set to `"managed"` to use Agora-managed credentials for the provider without supplying your own API key.
* **Updated parameters**
* [`properties.turn_detection.config.start_of_speech.mode`](/en/api-reference/api-ref/conversational-ai/join#properties-turn-detection-config-start-of-speech-mode): Now accepts `manual`, which disables automatic start-of-speech detection so the client can explicitly signal the start of user speech over RTM.
* [`properties.turn_detection.config.end_of_speech.mode`](/en/api-reference/api-ref/conversational-ai/join#properties-turn-detection-config-end-of-speech-mode): Now accepts `manual`, which disables automatic end-of-speech detection so the client can explicitly signal the end of user speech over RTM.
* [`properties.llm.greeting_message`](/en/api-reference/api-ref/conversational-ai/join#properties-llm-greeting-message): In addition to serving as a standalone greeting, this field is now required as a fallback when `greeting_audio_url` is configured, and is used to maintain conversation context when an audio greeting is interrupted.
* **Deprecated parameters**
* [`preset`](/en/api-reference/api-ref/conversational-ai/join#preset): Deprecated in favor of setting `credential_mode` to `"managed"` within the `asr`, `llm`, or `tts` block. See [Use managed mode](/en/ai/build/custom-model-integration/managed-mode).
* Changes to [**Notification event types**](/en/ai/reference/event-types)
* **Updated events**
* [`110 agent error`](/en/ai/reference/event-types#110-agent-error): Now reports greeting audio URL errors when the greeting audio file fails to download, times out, is in an unsupported format, or fails to decode. The agent automatically falls back to TTS synthesis using `greeting_message`. When `parameters.enable_error_message` is `true`, these errors are also delivered to the client via the `onMessageError` RTM callback.
### v2.8 [#v28]
Released on June 11, 2026.
#### New features [#new-features-1]
Included in this release:
* **Session data retention control**
By default, session interaction text and audio are temporarily retained for the minimum necessary period to support service operation, agent optimization, and troubleshooting. To disable data retention for a session, set [`properties.parameters.opt_out`](/en/api-reference/api-ref/conversational-ai/join#properties-parameters-opt-out) to `true` when starting an agent.
* **RTC token expiry warning**
A new [`104 agent expire`](event-types#104-agent-expire) event is triggered when the agent's RTC token is about to expire. Upon receiving this event, call the [Update agent](/en/api-reference/api-ref/conversational-ai/update) API with a new token to refresh it. The agent does not exit the channel or interrupt the session when this event is triggered.
* **New ASR provider**
* [xAI](../models/asr/xai)
* **New LLM provider**
* [xAI Grok](../models/llm/xai)
* **New TTS provider**
* [xAI](../models/tts/xai)
#### Improvements [#improvements-1]
This release includes the following enhancements.
Important
These changes may affect your existing integration. Review each change and update your integration as needed.
* **Idle timeout behavior change**
Setting `properties.idle_timeout` to `0` no longer means the agent runs indefinitely. From this release, `0` only disables the channel idle timeout. Regardless of the `idle_timeout` value, the maximum running time for a single session is 72 hours, after which the agent automatically exits.
If your integration previously relied on `idle_timeout = 0` to keep an agent running until manually stopped, update your task scheduling and lifecycle management logic to account for the 72-hour limit.
#### API changes [#api-changes-1]
This release introduces the following changes to the RESTful API.
* Changes to [**Start a conversational AI agent**](/en/api-reference/api-ref/conversational-ai/join)
* **New parameters**
* [`properties.parameters.opt_out`](/en/api-reference/api-ref/conversational-ai/join#properties-parameters-opt-out): Set to `true` to disable data retention for the current session.
* **Updated parameters**
* [`properties.idle_timeout`](/en/api-reference/api-ref/conversational-ai/join#properties-idle-timeout): Valid range is now `0` to `259200` seconds (72 hours). Regardless of the value set, the maximum running time for a single session is 72 hours.
* Changes to [**Retrieve agent history**](/en/api-reference/api-ref/conversational-ai/history)
* **New response fields**
* [`contents[].speech_start_ms`](/en/api-reference/api-ref/conversational-ai/history#contents-speech-start-ms): Unix timestamp in milliseconds indicating when the user started speaking or the agent started TTS playback. Only returned when `llm.vendor` is `custom`.
* [`contents[].speech_end_ms`](/en/api-reference/api-ref/conversational-ai/history#contents-speech-end-ms): Unix timestamp in milliseconds indicating when the user stopped speaking, or when TTS playback completed or was interrupted. Only returned when `llm.vendor` is `custom`.
* [`contents[].speech_algorithmic_delay`](/en/api-reference/api-ref/conversational-ai/history#contents-speech-algorithmic-delay): The total delay in milliseconds introduced by audio processing algorithms after audio is captured from the user's microphone. Only returned when `llm.vendor` is `custom`, `contents[].role` is `user`, and actual voice input is present.
* Changes to [**Notification event types**](event-types)
* **New events**
* [`104 agent expire`](event-types#104-agent-expire): Triggered when the agent's RTC token is about to expire. Call the Update agent API with a new token upon receiving this event.
* **Updated events**
* [`102 agent left`](event-types#102-agent-left): Added `Task lifetime limit exceeded` as a new exit reason when the 72-hour session limit is reached.
* [`103 agent history`](event-types#103-agent-history): Added `contents[].speech_start_ms`, `contents[].speech_end_ms`, and `contents[].speech_algorithmic_delay`. Only returned when `llm.vendor` is `custom`.
* Changes to [**Retrieve a list of agents**](/en/api-reference/api-ref/conversational-ai/list)
* **Updated parameters**
* `state`: Now accepts a comma-separated list of values to filter by multiple agent states in a single request. For example, `state=0,1,2`.
### v2.7 [#v27]
Released on May 20, 2026.
#### New features [#new-features-2]
Included in this release:
* **New MLLM provider**
* [xAI Grok](../models/mllm/xai)
* **New Avatar providers**
* [Generic avatar (Beta)](../models/avatar/generic)
* **Greeting interruption control**
The `properties.llm.greeting_configs` object in the [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join) API now supports an `interruptable` parameter, which controls whether user speech can interrupt a greeting during playback.
#### Improvements [#improvements-2]
This release includes the following enhancements:
Important
These changes may affect your existing integration. Review each change and update your integration as needed.
* **Response status codes updated**
This release adjusts the HTTP response status codes and `reason` values returned by the RESTful API. New status codes `401`, `429`, and `500` are added. New `reason` values `ServiceNotEnabled`, `AccountSuspended`, and `ResourceAllocationFailed` are introduced. The existing `InvalidRequest` reason is replaced by `InvalidRequestBody`, `MissingRequiredField`, and `InvalidFieldValue`. For details, see [Status codes and error messages](/en/api-reference/api-ref/conversational-ai/status-codes).
If your integration relies on `reason` values for error handling, retry logic, or alerting, update your logic to handle the new values accordingly.
* **Query conversation turns API now returns paginated results**
The [Query conversation turn information](/en/api-reference/api-ref/conversational-ai/turns) API no longer returns all turns in a single response. Results are now paginated, defaulting to page 1 with up to 50 turns per page.
This affects sessions with more than 50 conversation turns. Update your integration to handle the new `total_turn_count` and `pagination` fields in the response, and retrieve subsequent pages as needed.
This release also adds the [112 turns finished](event-types#112-turns-finished) notification event, which delivers conversation turn data in batches after a session ends as an alternative way to retrieve turn data.
* **`on_listening_action` default behavior change**
The default value of `on_listening_action` in the [Send a custom instruction](/en/api-reference/api-ref/conversational-ai/think) API has changed from `inject` to `interrupt`. When set to `interrupt`, the API immediately interrupts the current flow and initiates a new round of dialogue. If you rely on the previous default behavior, explicitly set `on_listening_action` to `inject` in your requests.
#### API changes [#api-changes-2]
This release introduces the following changes to the RESTful API.
* Changes to [**Start a conversational AI agent**](/en/api-reference/api-ref/conversational-ai/join)
* **New parameters**
* [`properties.llm.greeting_configs.interruptable`](/en/api-reference/api-ref/conversational-ai/join#properties-llm-greeting-configs-interruptable): Controls whether user speech can interrupt a greeting during playback.
* Changes to [**Query conversation turn information**](/en/api-reference/api-ref/conversational-ai/turns)
* **New query parameters**
Adds `page_index` and `page_size` fields to [query parameters](/en/api-reference/api-ref/conversational-ai/turns#query-parameters).
* **New response parameters**
The [response](/en/api-reference/api-ref/conversational-ai/turns#response) now includes additional top-level fields:
* `agent_id`: The unique identifier of the agent.
* `name`: The name of the agent.
* `channel`: The name of the RTC channel the agent joined.
* `total_turn_count`: The total number of dialogue turns in the session.
* `pagination`: Pagination details for the response, including `page_index`, `total_pages`, and `is_last_page`.
* Changes to [**Send a custom instruction**](/en/api-reference/api-ref/conversational-ai/think)
* **Behavior changes**
* [`on_listening_action`](/en/api-reference/api-ref/conversational-ai/think#on-listening-action): The default value has changed from `inject` to `interrupt`. When set to `interrupt`, the API immediately interrupts the current flow and initiates a new round of dialogue.
* Changes to [**Notification event types**](event-types)
* **New events**
* `112 turns finished`: Triggered after a session ends, delivering conversation turn data in batches for post-session analysis and processing.
### v2.6 [#v26]
Released on April 22, 2026.
#### New features [#new-features-3]
Included in this release:
* **New TTS provider**
* [Deepgram](../models/tts/deepgram)
* **New endpoint: Send a custom instruction**
Adds a new endpoint to inject a custom text instruction into the agent's current conversation pipeline. The instruction is processed as user input, enabling scenarios such as implicit instruction injection, client-side event triggering, and voice and text collaboration. For details, see [Send a custom instruction](/en/api-reference/api-ref/conversational-ai/think).
* **MLLM turn detection refactoring**
Introduces a new [`mllm.turn_detection`](/en/api-reference/api-ref/conversational-ai/join#properties-mllm-turn-detection) object for configuring turn detection when using MLLM. When defined, this overrides the top-level `turn_detection` object. Supported modes vary by vendor:
* OpenAI Realtime API: `agora_vad`, `server_vad`, `semantic_vad`
* Google Gemini Live: `agora_vad`, `server_vad`
* **Interruption control**
Adds a new top-level [`interruption`](/en/api-reference/api-ref/conversational-ai/join#properties-interruption) object for unified management of agent interruption behavior. Supports `start_of_speech` and `keywords` trigger modes, and configurable handling strategies when interruption is disabled.
#### Improvements [#improvements-3]
This release includes the following enhancements:
* **New agent state callbacks**
Adds three callbacks to the [Android](/en/api-reference/api-ref/conversational-ai/client-toolkit/android#onagentlisteningchanged), [iOS](/en/api-reference/api-ref/conversational-ai/client-toolkit/ios#onagentlisteningchanged), and [web](/en/api-reference/api-ref/conversational-ai/client-toolkit/web#econversationalaiapievents) Toolkit event handler interface:
* `onAgentListeningChanged`: Listen for changes in the agent's listening state to monitor when the agent starts or stops listening to user input.
* `onAgentThinkingChanged`: Listen for changes in the agent's thinking state to monitor when the agent starts or stops processing a request.
* `onAgentSpeakingChanged`: Listen for changes in the agent's speaking state to monitor when the agent starts or stops playing back speech.
* **Added `name` field to all [NCS event](event-types) payloads**
Returns the agent name provided when starting the agent.
#### API changes [#api-changes-3]
This release introduces the following changes to the RESTful API.
* **New endpoint**
* [Send a custom instruction](/en/api-reference/api-ref/conversational-ai/think): `POST /v2/projects/{appid}/agents/{agentId}/think`
* Changes to [**Start a conversational AI agent**](/en/api-reference/api-ref/conversational-ai/join)
* **New parameters**
* [`interruption`](/en/api-reference/api-ref/conversational-ai/join#properties-interruption): Unified interruption control configuration. Supports `start_of_speech` and `keywords` trigger modes, and configurable handling strategies when interruption is disabled.
* [`greeting_configs.delay_ms`](/en/api-reference/api-ref/conversational-ai/join#properties-llm-greeting-configs-delay-ms): Specifies the delay in milliseconds before the agent plays the greeting message after the user joins the channel.
* [`llm.headers`](/en/api-reference/api-ref/conversational-ai/join#properties-llm-headers): Allows you to pass custom headers in LLM requests, such as business-specific fields or tenant identifiers.
* [`mllm.enable`](/en/api-reference/api-ref/conversational-ai/join#properties-mllm-enable): Enables the MLLM module. Replaces the deprecated `advanced_features.enable_mllm`.
* [`mllm.turn_detection`](/en/api-reference/api-ref/conversational-ai/join#properties-mllm-turn-detection): Turn detection configuration for the MLLM module. When defined, overrides the top-level `turn_detection` object.
* [`mllm.turn_detection.server_vad_config.idle_timeout_ms`](/en/api-reference/api-ref/conversational-ai/join#properties-mllm-turn-detection-server-vad-config-idle-timeout-ms): Idle timeout in milliseconds for server VAD mode. Applicable to OpenAI Realtime API only.
* [`mllm.vendor`](/en/api-reference/api-ref/conversational-ai/join#properties-mllm-vendor) value `gemini`: Enables Google Gemini Live integration using the Gemini Developer API.
* **Behavior changes**
* `turn_detection` now handles SoS and EoS detection only. Interruption handling strategies have moved to the new top-level `interruption` field.
* `turn_detection.config.start_of_speech.mode` values `keywords` and `disabled` are deprecated. Use the new `interruption` field instead.
* When `turn_detection.end_of_speech.mode` is set to `semantic`, EOS detection supports English and Chinese only. For unsupported languages, the engine automatically falls back to VAD.
* **Deprecated fields**
* `advanced_features.enable_mllm`: Use `mllm.enable` instead.
* `turn_detection.config.start_of_speech.mode` value `keywords`: Use `interruption.mode = "keywords"` instead.
* `turn_detection.config.start_of_speech.mode` value `disabled`: Use `interruption.enable = false` with `interruption.disabled_config.strategy` instead.
* **Removed fields**
* `mllm.style`
* `mllm.create_response` and `mllm.interrupt_response` for OpenAI Realtime API.
* Changes to [**Notification event types**](event-types)
* **New fields**
* Added `name` to all [NCS event payloads](event-types). Returns the agent name provided when starting the agent.
### v2.5 [#v25]
Released on March 31, 2026.
#### New features [#new-features-4]
Included in this release:
* **Preset model configurations**
Adds a [`preset`](/en/api-reference/api-ref/conversational-ai/join#preset) parameter to the Join API. Pass a comma-separated list of preset names to apply predefined configurations for ASR, LLM, and TTS providers. When you use a preset, you do not need to provide the endpoint URL, API key, or model for the preset provider. Use the `asr`, `llm`, and `tts` fields to configure additional settings. When you use a preset, the corresponding ASR, LLM, or TTS service is provided through Agora-managed accounts. For details, see the [pricing](pricing) page.
Available presets:
* ASR: `deepgram_nova_2`, `deepgram_nova_3`
* LLM: `openai_gpt_4o_mini`, `openai_gpt_4_1_mini`, `openai_gpt_5_nano`, `openai_gpt_5_mini`
* TTS: `minimax_speech_2_6_turbo`, `minimax_speech_2_8_turbo`, `openai_tts_1`
* **Pause state detection**
When enabled, the agent uses semantic understanding to detect when a user intends to pause the conversation. For example, if the user says "hold on" or "just a moment", the agent waits for further input instead of forwarding the utterance to the LLM. Controlled by the new [`pause_state_enabled`](/en/api-reference/api-ref/conversational-ai/join#properties-turn-detection-config-end-of-speech--mode-config-pause-state-enabled) parameter.
* **Conversation turn insights**
You can now query per-turn start, end, and latency metrics for completed agent sessions. See [Query conversation turn information](/en/api-reference/api-ref/conversational-ai/turns).
* **Real-time TTS parameter updates with custom LLM**
In scenarios with a custom LLM integrated, this release adds support for real-time TTS parameter updates during conversations, enabling a more natural conversational experience. This feature applies to the following use cases:
* The custom LLM detects a user request for the agent to change its voice or speaking style.
* The custom LLM identifies a user's positive emotion and adjusts TTS volume, pitch, speech rate, and other parameters in real time to better match the user's mood.
For implementation details, refer to [Real-time TTS parameter updates](../build/custom-model-integration/custom-llm#update-tts-parameters-in-real-time).
* **New TTS provider**
* [Murf (Beta)](../models/tts/murf)
* **New Avatar provider**
* [Anam (Beta)](../models/avatar/anam)
#### Improvements [#improvements-4]
This release includes the following enhancements:
* **Migration from HeyGen API to LiveAvatar (Beta) API**
Updates the HeyGen avatar model configuration to use the [LiveAvatar (Beta)](../models/avatar/heygen) API.
#### API changes [#api-changes-4]
This release introduces the following changes to the RESTful API.
* **New endpoint: Query conversation turns**
Adds a new GET endpoint to query conversation turn information for an agent session. After a conversation ends, use this endpoint to retrieve the start event, end event, and latency metrics for each turn within the session. For details, see [Query conversation turn information](/en/api-reference/api-ref/conversational-ai/turns).
* Changes to [**Start a conversational AI agent**](/en/api-reference/api-ref/conversational-ai/join)
* **New parameters added**
* [`preset`](/en/api-reference/api-ref/conversational-ai/join#preset)
* [`pause_state_enabled`](/en/api-reference/api-ref/conversational-ai/join#properties-turn-detection-config-end-of-speech--mode-config-pause-state-enabled)
* **Deprecated fields deleted**
The following deprecated fields are deleted:
* `properties.silence_timeout`
* `properties.advanced_features.enable_aivad`
* `properties.llm.silence_message`
### v2.4 [#v24]
Released on February 2, 2026.
#### New features [#new-features-5]
Included in this release:
* **MCP integration**
Adds a [`llm.mcp_servers`](/en/api-reference/api-ref/conversational-ai/join#properties-llm-mcp-servers) field to the [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join) API to connect the LLM to an MCP (Model Context Protocol) server. Set [`advanced_features.enable_tools`](/en/api-reference/api-ref/conversational-ai/join#properties-advanced-features-enable-tools) to `true` to enable tool calls. This allows the agent to call tools provided by external services to extend functionality.
* **Filler phrases**
Adds a [`properties.filler_words`](/en/api-reference/api-ref/conversational-ai/join#properties-filler-words) field to insert pre-set or LLM-generated filler phrases during conversations to fill periods of silence while waiting for LLM output. This feature smooths dialogue flow, reduces user anxiety, and makes agent speech sound more natural.
#### Improvements [#improvements-5]
This release includes the following enhancements:
* **Turn detection configuration optimization**
The `turn_detection` parameter structure has been updated to provide more flexible conversation turn detection. The new structure uses a `mode` + `config` pattern with separate Start of Speech (SoS) and End of Speech (EoS) detection configurations.
**Basic configuration**
* `mode`: Conversation turn detection mode (currently supports `default`)
* `config.speech_threshold`: Voice activity detection sensitivity
**Start of Speech (SoS) detection**
Detects when the user begins speaking. When SoS is detected while the agent is speaking, an interruption occurs.
The [`config.start_of_speech`](/en/api-reference/api-ref/conversational-ai/join#properties-turn-detection-config-start-of-speech) parameter supports three detection modes:
* **VAD mode** (`vad`): Triggered by voice activity detection. Supports configuration of interruption threshold, prefix padding, and ignore word list.
* **Keyword mode (Beta)** (`keywords`): Based on keyword triggering. The agent begins conversation after detecting a specified keyword.
* **Disabled mode** (`disabled`): Disables interruption. Supports append or ignore strategies.
**End of Speech (EoS) detection**
Detects when the user finishes speaking. When EoS is detected, the agent generates a response.
The [`config.end_of_speech`](/en/api-reference/api-ref/conversational-ai/join#properties-turn-detection-config-end-of-speech) parameter supports two detection modes:
* **VAD mode** (`vad`): Determines conversation end based on silence duration.
* **Semantic mode** (`semantic`): Determines conversation end based on semantic understanding. Supports configurable maximum wait time.
#### API changes [#api-changes-5]
This release introduces the following changes to the RESTful API.
* Changes to [**Start a conversational AI agent**](/en/api-reference/api-ref/conversational-ai/join)
* **[`turn_detection`](/en/api-reference/api-ref/conversational-ai/join#properties-turn-detection) has a revamped structure**
All previous fields under `turn_detection` have been deprecated and replaced with a new data structure.
* Deprecated
* `turn_detection.interrupt_mode`
* `turn_detection.interrupt_keywords`
* `turn_detection.interrupt_duration_ms`
* `turn_detection.prefix_padding_ms`
* `turn_detection.silence_duration_ms`
* `turn_detection.threshold`
* **AIVAD activation**
The `advanced_features.enable_aivad` field is now deprecated; to configure AIVAD activation use [`turn_detection.config.end_of_speech.mode = "sematic"`](/en/api-reference/api-ref/conversational-ai/join#properties-turn-detection-config-end-of-speech-mode).
* **New parameters added**
* [`advanced_features.enable_tools`](/en/api-reference/api-ref/conversational-ai/join#properties-advanced-features-enable-tools)
* [`llm.mcp_servers`](/en/api-reference/api-ref/conversational-ai/join#properties-llm-mcp-servers)
* [`properties.filler_words`](/en/api-reference/api-ref/conversational-ai/join#properties-filler-words)
* [`turn_detection.mode`](/en/api-reference/api-ref/conversational-ai/join#properties-turn-detection-mode)
* [`turn_detection.config`](/en/api-reference/api-ref/conversational-ai/join#properties-turn-detection-config)
### v2.3 [#v23]
Released on January 7, 2026.
#### New features [#new-features-6]
Included in this release:
* **Control LLM response interruption in custom LLM scenarios**
Adds a `metadata.interruptable` field in the first chunk of the `chat.completion.custom_metadata` object to control whether user speech can interrupt the agent's TTS output when using custom LLMs with streaming response (SSE). Use this to prevent interruptions when delivering critical information such as regulations, policies, or pricing. For more information, see [Configure LLM response interruption](../build/custom-model-integration/custom-llm#configure-llm-response-interruption).
* **RTC media content encryption**
Adds an `rtc` parameter to the [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join) API for configuring RTC encryption.
* **New ASR provider**
* [Sarvam (Beta)](../models/asr/sarvam)
#### Improvements [#improvements-6]
This release includes the following enhancements:
* **ElevenLabs TTS compatibility Optimization**
Supports SSML parsing and forwarding to ensure ElevenLabs TTS correctly renders intended speech effects such as pauses, emphasis, and pronunciation.
* **Latency optimization best practices**
Adds a guide to [Optimize conversation latency](../best-practices/optimize-latency).
* **Cloud Recording best practices**
Adds a guide to [Record conversations with Cloud Recording](../best-practices/record-agent-conversation).
#### API changes [#api-changes-6]
This release introduces the following changes to the RESTful API.
* **Stop agent API is now asynchronous**
The [Stop a conversational AI agent](/en/api-reference/api-ref/conversational-ai/leave) API now responds immediately after request parameters are validated. The request is processed asynchronously after the API returns.
* Changes to [**Start a conversational AI agent**](/en/api-reference/api-ref/conversational-ai/join)
* **New parameters added**
* `properties.rtc`
### v2.2 [#v22]
Released on December 15, 2025.
#### New features [#new-features-7]
Included in this release:
* **Geofencing**
Use `geofence` configuration when you [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join) to limit which Agora servers the Conversational AI Engine can access based on geographic regions. See [Restrict agent zones](../best-practices/regional-restrictions) for details.
* **Agent greeting mode**
Adds a field `llm.greeting_configs.mode` to the [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join) request to set the agent's greeting broadcast mode. The following modes are supported:
* `single_every` (Default): The agent broadcasts a greeting every time a user joins a channel where there are no other users.
* `single_first`: The agent broadcasts a greeting only when the first user joins a channel.
* **New TTS providers**
* [Sarvam (Beta)](../models/tts/sarvam)
#### Improvements [#improvements-7]
This release includes the following enhancements:
* **TTS parameter update**
Adds `base_url` field to the TTS parameters for [OpenAI](../models/tts/openai) and [ElevenLabs](../models/tts/elevenlabs). This field specifies the endpoint URL for the TTS service.
#### API changes [#api-changes-7]
This release introduces the following changes to the RESTful API.
* Changes to [**Start a conversational AI agent**](/en/api-reference/api-ref/conversational-ai/join)
* **New parameters added**
* `properties.geofence`
* `llm.greeting_configs.mode`
### v2.1 [#v21]
Released on December 5, 2025.
#### New features [#new-features-8]
Included in this release:
* **Template variables**
This version adds the `llm.template_variables` field to the [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join) API, used to insert variables into the agent's `system_messages`, `greeting_message`, `failure_message`, and `parameters.silence_config.content` text. By configuring these variables, the Conversational AI engine automatically replaces them with the corresponding values defined in `llm.template_variables`. Template variables, combined with prompt customization and SIP outbound calling functionality, enable you to dynamically inject content to automate processes such as automatic hang-up, voicemail recognition, automatic message leaving, and call transfer.
* **Custom labels**
This version adds a `labels` field to the [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join) API, enabling agents to carry custom labels. These labels are bound to the agent and returned in the `payload` field of all message notification callbacks from the Conversational AI engine, allowing you to implement custom business logic, such as tagging activity IDs, customer groups, and business scenarios.
In SIP outbound call scenarios, you can pass a custom label in the `properties.labels` field when calling the outbound call interface to mark the call.
#### API changes [#api-changes-8]
This release introduces the following changes to the RESTful API.
* Changes to [**Start a conversational AI agent**](/en/api-reference/api-ref/conversational-ai/join)
* **New parameters added**
* `properties.labels`
* `llm.template_variables`
### v2.0 [#v20]
Released on November 15, 2025.
#### New features [#new-features-9]
Included in this release:
* **Telephony (Beta)**
This version adds an outbound calling feature that enables the conversational AI agent to initiate an outbound call to a specified number through a POST API. After the call is answered, the agent can engage in real-time dialogue with the callee. This feature supports a wide range of outbound AI call scenarios.
A set of phone number management APIs is also added for handling numbers connected to Conversational AI Engine. Refer to the API changes section for details.
Telephony pricing
The telephony feature is currently in **Beta** and is provided free of charge. Pricing terms may change upon official release.
* **Selective attention locking (Beta)**
This version adds the selective attention lock feature. Register voiceprints to enable the agent to identify specific speakers and suppress background voices and environmental noise, ensuring clearer, more focused conversations.
* **Graceful exit**
This version adds a new `farewell_config` field to [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join) API to configure the graceful exit feature. When enabled, calling the [Stop a Conversational Agent](/en/api-reference/api-ref/conversational-ai/leave) API causes the agent to enter an `IDLE` state before leaving the channel.
* **Keyword interruption mode**
This release adds a new option to the `turn_detection.interrupt_mode` field in the [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join) API. Set this field to `"keyword"` to enable keyword interruption mode.
When this mode is enabled, the agent stops its current behavior after detecting any of the keywords specified in the `turn_detection.interrupt_keywords` field.
* **Adaptive interruption mode**
This release adds a new option to the `turn_detection.interrupt_mode` field in the [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join) API. Set this field to `"adaptive"` to enable adaptive interruption mode.
When this mode is enabled, the agent dynamically increases the voice continuity threshold while speaking to reduce accidental interruptions.
* **New ASR, LLM, MLLM, and TTS providers**
* ASR
* [OpenAI (Beta)](../models/asr/openai)
* [Speechmatics](../models/asr/speechmatics)
* [Google (Beta)](../models/asr/google)
* Amazon Transcribe (Beta)
* [AssemblyAI (Beta)](../models/asr/assembly-ai)
* LLM
* [Groq](../models/llm/groq)
* [Amazon Bedrock](../models/llm/amazon)
* MLLM
* [Google Gemini Live](../models/mllm/gemini)
* TTS
* [Rime (Beta)](../models/tts/rime)
* [Fish Audio (Beta)](../models/tts/fish-audio)
* [Google (Beta)](../models/tts/google)
* [Amazon Polly (Beta)](../models/tts/amazon)
* **New webhook notification events**
This release adds three new webhook notification event types to support metrics reporting and call-state monitoring:
* `111`: [agent metrics](event-types#111-agent-metrics)
Notifies real-time performance metrics for each dialogue turn, including ASR, LLM, and TTS latency measurements.
* `201`: [inbound call state](event-types#201-inbound-call-state)
Reports state changes for incoming calls, such as when a call starts, is answered, transferred, or hung up.
* `202`: [outbound call state](event-types#202-outbound-call-state)
Reports state changes for outbound calls initiated by the agent, including call start, dialing, ringing, answer, and hang-up events.
#### Improvements [#improvements-8]
This release includes the following enhancements:
* **Support for avatars with MLLMs**
Added support for using avatars with MLLMs.
#### API changes [#api-changes-9]
This release introduces the following changes to the RESTful API.
* Changes to [**Start a conversational AI agent**](/en/api-reference/api-ref/conversational-ai/join)
* **New parameters added**
* `properties.parameters.farewell_config`
* `properties.advanced_features.enable_sal`
* `properties.sal`
* `properties.sal.sal_mode`
* `properties.sal.sample_urls`
* `properties.turn_detection.interrupt_mode` (supports `adaptive` and `keyword` values)
* `properties.turn_detection.interrupt_keywords`
* `properties.turn_detection.interrupt_duration_ms` (migrated from `vad.interrupt_duration_ms`)
* `properties.turn_detection.prefix_padding_ms` (migrated from `vad.prefix_padding_ms`)
* `properties.turn_detection.silence_duration_ms` (migrated from `vad.silence_duration_ms`)
* `properties.turn_detection.threshold` (migrated from `vad.threshold`)
* **Deprecated**
* The `vad` interface is deprecated. All configuration items have been moved to the `turn_detection` field.
* **New APIs**
* Telephony (Beta)
* Initiate an outbound call
* Retrieve call records
* Hang-up a call
* Retrieve call status
* Phone number management (Beta)
* Retrieve list of numbers
* Import number
* Retrieve number information
* Update number configuration
* Delete number
#### Toolkit API [#toolkit-api]
This release renames all APIs and parameters containing the word `transcription` in the client-side subtitle API to use `transcript`, as shown below:
#### Android [#android]
* `onTranscriptionUpdated` renamed to `onTranscriptUpdated`
* `TranscriptionRenderMode` renamed to `TranscriptRenderMode`
* `TranscriptionType` renamed to `TranscriptType`
* `TranscriptionStatus` renamed to `TranscriptStatus`
* `Transcription` renamed to `Transcript`
#### iOS [#ios]
* `onTranscriptionUpdated` renamed to `onTranscriptUpdated`
* `TranscriptionRenderMode` renamed to `TranscriptRenderMode`
* `TranscriptionType` renamed to `TranscriptType`
* `TranscriptionStatus` renamed to `TranscriptStatus`
* `Transcription` renamed to `Transcript`
#### Web [#web]
* `TRANSCRIPTION_UPDATED` renamed to `TRANSCRIPT_UPDATED`
### v1.7 [#v17]
Released on July 31, 2025.
#### New features [#new-features-10]
* **AI avatars**
Create visual avatar representations for your conversational agents using third-party avatar providers. AI avatars provide a visual presence during voice interactions, making conversations feel more natural and engaging. Enable AI avatars by setting `avatar.enable` to `true` and configuring the `avatar.vendor` and `avatar.params` fields when calling [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join) to create your agent.
Info
AI avatars require video streaming and incur additional charges. See [video calling pricing](../../realtime-media/video/reference/pricing) for details.
* **Selective attention locking (Beta)**
This version introduces the selective attention locking feature, which uses voiceprint recognition technology to identify and filter out the speaker while suppressing background noise. This enhances the efficiency of conversational AI, particularly improving speech recognition accuracy. To experience this feature, contact [technical support](mailto\:support@agora.io).
* **Send picture messages (Beta)**
The toolkit now includes an API for [sending picture messages](../build/send-multimodal-messages). You can send image URLs to the main model, which automatically references the image in future interactions to generate more relevant responses. A new callback is available to receive image message receipt details after successful transmission.
Info
* The picture messaging feature is currently in Beta and free for a limited time.
* Image processing depends on the capabilities of the integrated LLM. Ensure the LLM you connect to the Conversational AI Engine supports image input.
#### API changes [#api-changes-10]
This release introduces the following modifications to the RESTful API.
* [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join)
* **New parameters added**
* `avatar.enable`
* `avatar.vendor`
* `avatar.params`
##### Toolkit API [#toolkit-api-1]
* Android:
* `chat`
* `ImageMessage`
* `onMessageReceiptUpdated`
* `MessageReceipt`
* iOS:
* `chat`
* `ChatMessage`
* `ChatMessageType`
* `ImageMessage`
* `onMessageReceiptUpdated`
* `MessageReceipt`
* Web:
* `chat`
* `TMessageReceipt`
* `EChatMessagePriority`
* `EChatMessageType`
* `IChatMessageBase`
* `IChatMessageImage`
### v1.6 [#v16]
Released on July 15, 2025.
#### New features [#new-features-11]
* **Support for OpenAI realtime API**
Integrate Multimodal Large Language Models (MLLMs) with Conversational AI Engine to enable end-to-end real-time audio and text interactions. See [OpenAI Realtime API](../models/mllm/openai) for integration details.
* **Support for more TTS vendors**
Conversational AI Engine now supports the following additional TTS vendors:
* [Cartesia](../models/tts/cartesia)
* [OpenAI](../models/tts/openai)
* **Custom ASR provider support**
To improve flexibility in configuring conversational agents, this release allows you to select a custom automatic speech recognition (ASR) provider. The [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join) API now includes the following new parameters:
* `asr.vendor`: Specify the ASR provider
* `asr.params`: Configure ASR parameters
The following ASR providers are supported:
* **ARES** (default)
* **Microsoft Azure**
* **Deepgram**
**Billing update:**
In earlier versions, the service fee included the cost of the Ares ASR provider. Starting in v1.6, the pricing is restructured as follows:
* If you use **ARES ASR**, the total price remains unchanged:
***Total cost = Conversational AI Engine Audio Basic Task + ARES ASR Task***
* If you use **a different ASR provider**, you are charged **only** the new **Conversational AI Engine Audio Basic Task** fee.
For further details, see [Pricing](pricing).
* **Multi-platform toolkit**
Agora now offers a toolkit to help you quickly build conversational agent apps. The toolkit is available for [**iOS**](/en/api-reference/api-ref/conversational-ai/client-toolkit/ios), [**Android**](/en/api-reference/api-ref/conversational-ai/client-toolkit/android), and [**Web**](/en/api-reference/api-ref/conversational-ai/client-toolkit/web), and includes APIs for common scenarios. Call these APIs to combine the capabilities of the Agora Voice SDK and Signaling SDK to achieve the following functions:
* [**Display live transcript**](../build/transcripts)
Display real-time text output of user-agent conversations. The transcript component is now more robust, with better error handling, session management, and extensibility.
* [**Interrupt the agent**](../build/shape-the-conversation/interrupt-agent)
Stop the agent from speaking or thinking mid-conversation.
* [**Client-side events**](../build/handle-runtime-events/event-notifications)
Track changes in conversation state, performance metrics, and error events.
* [**Optimize audio settings**](../best-practices/audio-setup)
Quickly apply best-practice audio configurations to improve agent responsiveness and clarity.
#### API changes [#api-changes-11]
##### REST API [#rest-api]
This release introduces several important modifications to the RESTful API.
* [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join)
* **New parameters added**
* `asr.vendor`
* `asr.params`
* `advanced_features.enable_mllm`
* `properties.mllm`
* `turn_detection.type`
* `turn_detection.interrupt_duration_ms`
* `turn_detection.prefix_padding_ms`
* `turn_detection.silence_duration_ms`
* `turn_detection.threshold`
* `turn_detection.create_response`
* `turn_detection.interrupt_response`
* `turn_detection.eagerness`
* `parameters.enable_metrics`
* `parameters.data_channel`
* `parameters.enable_error_message`
##### Toolkit APIs [#toolkit-apis]
* [Android SDK](/en/api-reference/api-ref/conversational-ai/client-toolkit/android)
* [iOS SDK](/en/api-reference/api-ref/conversational-ai/client-toolkit/ios)
* [Web SDK](/en/api-reference/api-ref/conversational-ai/client-toolkit/web)
### v1.5 [#v15]
Released on Jun 9, 2025.
#### New features [#new-features-12]
* **Voice interruption mode**
This release adds the `turn_detection.interrupt_mode` parameter to the [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join) API, allowing you to control how the agent handles human voice interruptions. The following modes are supported:
* **`interrupt`**: (Default) The human voice immediately interrupts the agent. The agent terminates the current interaction and processes the new human voice input.
* **`append`**: The human voice does not interrupt the agent. The agent processes the newly received human voice request after the current interaction ends.
* **`ignore`**: The agent ignores human voice requests received during speaking or thinking. These requests are discarded and not stored in the context.
* **TTS filtering**
This release adds the `tts.skip_patterns` parameter to the [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join) API. This parameter controls whether the TTS module skips bracketed content when reading LLM response text. This prevents the agent from vocalizing structural prompt information like tone indicators, action descriptions, and system prompts, creating a more natural and immersive listening experience.
#### API changes [#api-changes-12]
This release introduces several important modifications to the RESTful API.
* [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join)
* **New parameters added**
* `turn_detection.interrupt_mode`
* `parameters.silence_config`
* `tts.skip_patterns`
### v1.4 [#v14]
Released on May 29, 2025.
#### New features [#new-features-13]
* **Metadata support for LLM requests**
This release adds the `llm.vendor` parameter to the [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join) API. When set to `"custom"`, the agent includes additional metadata when calling the LLM, such as `turn_id` and `timestamp`.
* **Support for Anthropic**
Conversational AI Engine now supports `anthropic` as a request style for chat completion. Refer to the `llm.style` parameter in [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join).
#### Improvements [#improvements-9]
This release includes the following enhancements:
* **Advanced LLM configuration**: The [Update agent configuration](/en/api-reference/api-ref/conversational-ai/update) API now supports:
* `llm.system_messages` for updating system prompts
* `llm.params` for modifying configuration parameters used when calling the large language model
#### API changes [#api-changes-13]
This release introduces several important modifications to the RESTful API.
* [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join)
* **New parameters added**
* `llm.vendor`
* **Removed parameters:**
* `agent_rtm_uid`
* [Update agent configuration](/en/api-reference/api-ref/conversational-ai/update)
* **New parameters added**
* `llm.system_messages`
* `llm.params`
### v1.3 [#v13]
Released on April 16, 2025.
#### New features [#new-features-14]
* **Agent conversation history**: This version adds two methods to retrieve an agent's history. The history includes messages exchanged between the user and the agent and timestamps of agent creation and exit.
* Call the RESTful API `history` endpoint to [Retrieve agent history](/en/api-reference/api-ref/conversational-ai/history).
* Subscribe to the [agent history event](event-types#103-agent-history) through the [Agora message notification service](../build/handle-runtime-events/webhooks). When the agent stops, Agora automatically sends the agent's history to your business server through a Webhook callback.
#### Improvements [#improvements-10]
* **Customize the priority of broadcast information**: This version upgrades the [Broadcast a message using TTS](/en/api-reference/api-ref/conversational-ai/speak) interface and adds two new configuration parameters related to broadcast interruption logic:
* `priority`: Sets the priority of the message broadcast. Supports setting the following priorities:
* `INTERRUPT` High priority
* `APPEND`: Medium priority
* `IGNORE`: Low priority
* `interruptable`: Configure whether to allow human voice to interrupt the agent's broadcast.
#### API changes [#api-changes-14]
* Adds the [Retrieve agent history](/en/api-reference/api-ref/conversational-ai/history) method.
* Adds `priority` and `interruptable` parameters to the [Broadcast a message using TTS](/en/api-reference/api-ref/conversational-ai/speak) method.
### v1.2 [#v12]
Released on April 10, 2025.
#### New features [#new-features-15]
* **Broadcast a message using TTS**: A new message broadcast interface enables a specified agent to deliver a custom message. When interacting with an agent, calling this interface interrupts the agent's speech and thinking process, allowing the TTS module to immediately broadcast the custom message.
* **Interrupt the agent**: The interrupt agent endpoint allows you to stop the specified agent's speech and thinking process.
#### API changes [#api-changes-15]
This version adds the following APIs:
* [Broadcast a message using TTS](/en/api-reference/api-ref/conversational-ai/speak)
* [Interrupt the agent](/en/api-reference/api-ref/conversational-ai/interrupt)
### v1.1 [#v11]
Released on March 27, 2025.
#### New features [#new-features-16]
The [Start a conversational agent](/en/api-reference/api-ref/conversational-ai/join) API adds the `enable_rtm` and `agent_rtm_uid` parameters to enable Signaling integration with the conversational AI agent. When this feature is enabled, the agent can leverage the Signaling SDK to obtain a user's custom context information such as speaking status, selected text, signature, and score, and pass this data to the agent to generate more relevant content. For details, see [Transmit custom information](../build/shape-the-conversation/custom-information).
#### Improvements [#improvements-11]
To help you quickly integrate a custom large language model (LLM), this version adds documentation for [Custom LLMs](../build/custom-model-integration/custom-llm). Refer to the sample code in the documentation to integrate your custom model into the Conversational AI Engine and enable advanced capabilities such as Retrieval-Augmented Generation (RAG), multi-modal processing, and tool invocation.
#### API changes [#api-changes-16]
The `POST` method to [Start a conversational AI agent](/en/api-reference/api-ref/conversational-ai/join) now includes the `enable_rtm` and `agent_rtm_uid` parameters.
### v1.0 (Public Beta) [#v10-public-beta]
This version, released on March 4, 2025, adds pricing information for the Agora Conversational AI Engine. For more information, see [Pricing](pricing).
#### Integration guide [#integration-guide]
To achieve the best conversation experience, use Agora Conversational AI Engine with the following Agora SDKs:
* Agora RTC Native SDK, v4.5.1 or later.
* Agora RTC Web SDK, version 4.23.2 or later.
#### New features [#new-features-17]
* **Live transcripts**: Supports real-time text output of conversations between users and the AI agent for transcript display in your app's UI. Agora provides an open-source transcript processing module. Simply integrate the module and call its API to implement live transcript. For details, see [Display live transcripts](../build/transcripts).
* **Message Notification Service**: Introduces a new Conversational AI Engine message notification service. Configure it in the Agora console and subscribe to agent creation, stop, and error events. When a subscribed event occurs, Agora sends the details to your specified callback address. See [Receive event notifications](../build/handle-runtime-events/webhooks).
* **Keywords**: Enhances recognition accuracy of Conversational AI Engine for proprietary words by adding keywords. This feature is currently in Beta stage. For details, [contact technical support](https://agoraio.zendesk.com/hc/en-us).
### v1.0 (Private Beta) [#v10-private-beta]
Released on February 18, 2025. The first beta release of the Conversational AI Engine brings natural, smooth, low-latency, and highly reliable real-time voice conversations with AI agents to Agora channels. It enables you to efficiently build intelligent and immersive interactive experiences. See [Product overview](/en/ai) for details.
#### Core Features [#core-features]
* **Real-time voice conversation**
Supports natural and smooth real-time voice conversations with AI. It delivers a low-latency, ultra-responsive interactive experience as if the user is communicating with a real person.
* **Intelligent noise suppression**
Intelligently identifies and suppresses background noise, ensuring clear sound transmission even in noisy environments to provide users with a high-quality audio experience.
* **Background human voice suppression**
Suppresses background voices and noise while accurately preserving the primary speaker's voice. This ensures a clear and focused interactive experience in multi-speaker environments.
* **Intelligent interruption handling**
Allows users to interrupt AI at any time to ensure quick and natural responses. This feature enables smooth transitions and avoids mechanical interactions.
* **Intelligent transmission**
An AI-optimized transmission algorithm ensures stable voice data delivery even in weak network conditions where packet loss reaches 80%. This guarantees conversation continuity and reliability across diverse network environments.
* **Flexible arrangement**
Supports multiple Large Language Model (LLM) and Text-to-Speech (TTS) providers, enabling flexible orchestration to meet diverse business needs and deliver highly customizable AI dialogue solutions.
* **Multi-platform support**
Compatible with iOS, Android, Web, and various embedded hardware platforms, providing a seamless and consistent cross-platform experience.
#### Integration guide [#integration-guide-1]
* For the best conversational experience, Agora recommends using Conversational AI Engine with specific Agora Video/Voice SDK versions. For details, [contact technical support](mailto\:support@agora.io).
* The number of Peak Concurrent Users (PCU) allowed to call the server API under a single App ID is limited to 20. If you need to increase this limit, please [contact technical support](mailto\:support@agora.io).
# TEN Framework core concepts (/en/ai/ten-agent/core-concepts)
## TEN runtime [#ten-runtime]
The TEN runtime is an execution environment designed for running applications within the TEN Framework. It provides a flexible architecture that supports extensions developed in multiple programming languages and handles various types of data streams. By managing the lifecycle of extensions, data flows, and threads, the TEN runtime offers developers a powerful tool for building a wide range of applications and services.

## App [#app]
An app in the TEN Framework can operate as a standalone process or as a thread within an existing process. This flexibility allows for versatile deployment options depending on the needs of the application.
## Extension group [#extension-group]
An extension group is a mechanism that designates a specific thread for execution. All extensions within a given group are executed on the same thread, ensuring consistent and synchronized processing.
## Extension [#extension]
An extension is the fundamental building block of the TEN Framework. Developers can create extensions in various programming languages and combine them to build different applications and scenarios. The framework's design supports cross-language collaboration, enabling extensions written in different languages to work together seamlessly within the same application or service.
For example, a developer might use C++ to create an extension for real-time communication (RTC) due to its performance benefits in handling audio and video data, while developing an AI extension in Python to take advantage of its robust libraries for data analysis and machine learning. These extensions can then be integrated into a single application, leveraging the strengths of each language.
## Graph [#graph]
A graph in the TEN Framework describes the data flow between extensions. It orchestrates how data moves from one extension to another, defining the participants and the flow of data between them. For example, you can route the output of a speech-to-text (STT) extension to a large language model (LLM) extension for further processing.
The TEN Framework supports four main types of data flows between extensions:
* Command
* Data
* Video frame
* Audio frame
By defining these data flows within a graph, you can create inter-extension communication and unidirectional data streams, particularly useful for handling audio and video data.
## Component hierarchy [#component-hierarchy]
The TEN Framework uses a nested structure where apps contain graphs, graphs contain extension groups, and extension groups contain extensions. Understanding this hierarchy is essential for building and configuring TEN applications.

* **App**:
An app can execute multiple graphs, which can be either statically predefined or dynamically assembled.
* **Graph**:
A graph is formed by multiple extensions working together to create a meaningful scenario. Each graph instance operates as a session within the app.
* **Extension group**:
The concept of an extension group is analogous to a thread. Extensions written in the same language and within the same extension group run on the same thread during runtime. You do not need to manage threads directly; simply declare the group to which each extension belongs.
* **Extension**:
Each extension within the framework is assigned a unique ID, structured as: `app-uri/graph-name/group-name/extension-name`
## TEN cloud store [#ten-cloud-store]
The TEN cloud store functions similarly to Google Play Store or Apple's App Store, providing a marketplace for extensions. You can share your own extensions or download those created by others. Integrate these extensions into TEN apps to facilitate development and expand functionality.

## TEN manager [#ten-manager]
The TEN manager is a tool that simplifies the management of extensions. It handles tasks such as uploading, sharing, and installing extensions, automatically managing dependencies between them and their environment. This makes the installation and publication of extensions convenient and efficient, and streamlines the development process within the TEN Framework.
# TEN Framework overview (/en/ai/ten-agent/framework-overview)
The TEN Framework, or Transformative Extensions Network, is an open-source framework for building real-time multimodal AI agents. It helps you create applications that process voice, video, data streams, images, and text at the same time.
TEN is designed for agents that need to think, listen, see, and respond in real time. It provides a graph-based runtime, reusable extensions, and multi-language development support so you can assemble complex AI systems without rebuilding every capability from scratch.
## What you can build [#what-you-can-build]
Use the TEN Framework to build real-time AI applications such as:
* Voice and video AI agents
* Simultaneous interpretation systems
* Speech-to-text and text-to-speech pipelines
* Multilingual communication experiences
* Audio-visual assistants and virtual companions
* AI-generated meeting minutes
* Language tutoring and coaching applications
## Key capabilities [#key-capabilities]
### Real-time multimodal interactions [#real-time-multimodal-interactions]
TEN optimizes interaction between extensions to support low-latency AI applications. A graph can process multiple data types, including audio, video, commands, structured data, images, and text.
### Multi-language and cross-platform development [#multi-language-and-cross-platform-development]
You can build modular extensions in C++, Go, and Python. TEN applications can run across Windows, macOS, Linux, and mobile environments, which gives you flexibility when choosing where each capability should execute.
### Modular extension architecture [#modular-extension-architecture]
TEN applications are composed from extensions. Each extension owns a focused capability, such as audio capture, ASR, LLM processing, TTS, tool calling, or output rendering. This modular architecture makes it easier to prototype, replace providers, and add new capabilities over time.
### Edge-cloud integration [#edge-cloud-integration]
TEN supports applications that combine edge and cloud components. You can run latency-sensitive or privacy-sensitive work near the user while using cloud-based models for heavier reasoning or generation tasks.
### Flexible orchestration beyond model limits [#flexible-orchestration-beyond-model-limits]
TEN lets you combine AI models with databases, retrieval systems, monitoring tools, and external services. Instead of depending on one model to handle the whole workflow, you can orchestrate multiple specialized components through the graph.
### Real-time agent state management [#real-time-agent-state-management]
TEN graphs can manage changing agent state so applications can adapt behavior during a session. This is useful for interactive agents that need to respond to user context, media state, or tool results while the conversation is still running.
## How it relates to TEN Agent [#how-it-relates-to-ten-agent]
TEN Agent is built on the TEN Framework. The framework provides the runtime, graph model, extension system, schema system, and package mechanics. TEN Agent applies those framework capabilities to conversational AI workflows.
Start with [Project overview](./project-overview) when you want to understand the TEN Agent repository layout, runtime configuration, extension folders, and local server API.
For implementation details, see:
* [Subgraphs](./architecture/subgraphs)
* [Schema system](./architecture/schema-system)
* [Type system](./architecture/type-system)
* [Development workflow](./develop/development-workflow)
## Samples [#samples]
Explore TEN Framework examples in the [TEN Framework repository](https://github.com/TEN-framework/ten-framework/tree/main/ai_agents/agents/examples).
# Project overview (/en/ai/ten-agent/project-overview)
The TEN Agent project is built on the TEN Framework and follows a modular architecture. This page describes the project structure, configuration system, and web server API.
TEN Agent uses a graph-based configuration system built on TEN Framework concepts. The project organizes code into modular extensions that can be orchestrated through configuration files without modifying source code.
The folder structure follows this pattern:
```text
├── agents/
│ └── ten_packages/
│ ├── extension/
│ │ ├── openai_chatgpt_python/
│ │ │ ├── extension.py
│ │ │ ├── ..
│ │ │ └── requirements.txt
│ │ ├── elevenlabs_tts
│ │ └── ..
│ └── property.json
├── playground
├── demo
└── server
```
It contains the following important folders and files:
* `property.json`: This file contains the orchestration of extensions. It is the main runtime configuration file.
* `ten_packages/extension`: This folder contains the extension modules. Each extension module is a separate Python/Golang/C++ package.
* `server`: This folder contains the web server code. It is responsible for handling the incoming requests and start/stop of agent processes.
* `playground`: This folder contains the UI code for the playground. It is a web-based interface to interact with the agent.
## Runtime configuration [#runtime-configuration]
The main runtime configuration file is `property.json`. It contains the orchestration of extensions. The file is structured as follows:
```json
"predefined_graphs": [
{
"name": "va_openai_azure_fashionai",
"auto_start": false,
"connections": [
// ...
],
"nodes": [
// ...
]
},
// ...
]
```
The file contains the following orchestration info:
* **Graphs**: Collections of nodes and connections that determine agent behavior
* **Nodes**: Individual extension instances with specific configurations
* **Connections**: Data flow paths between nodes
```json
{
"predefined_graphs": [
{
"name": "va_openai_azure_fashionai",
"auto_start": false,
"connections": [...],
"nodes": [...]
}
]
}
```
### Graphs [#graphs]
The `predefined_graphs` property contains a list of available graphs. Each graph defines how the agent behaves in a specific scenario. Each graph contains:
* `name`: Unique identifier for the graph
* `auto_start`: Whether to start this graph automatically
* `nodes`: List of extension instances
* `connections`: Data routing between nodes
### Nodes [#nodes]
The `nodes` section contains the list of extensions that are part of the graph. Each node includes the following:
* `name`: Unique identifier of a node within the graph
* `addon`: Specifies the extension module to use. You can create multiple instances of the same extension within a graph. For example, you might include several `chatgpt_openai_python` nodes, each using the same `addon` property but with unique `name` properties. This allows you to run parallel instances of an extension with different configurations or behaviors.
* `property`: Extension-specific configuration
```json
{
"name": "chatgpt_openai_python",
"addon": "chatgpt_openai_python",
"property": {
"api_key": "${env:OPENAI_API_KEY|}",
"model": "gpt-3.5-turbo",
"temperature": 0.5,
"max_tokens": 100,
"prompt": "You are a helpful assistant"
}
}
```
#### Node properties [#node-properties]
The `property` section of a node configures the extension's behavior. Each extension defines its available properties in the `manifest.json` file within its folder. You can customize an extension by setting these runtime properties.
#### Read environment variables [#read-environment-variables]
Many extensions require API keys to function. Instead of hardcoding sensitive keys in `property.json`, use environment variables with the syntax `${env:|}`. The following example shows how to read and use the `OPENAI_API_KEY` environment variable:
```json
{
"name": "chatgpt_openai_python",
"addon": "chatgpt_openai_python",
"property": {
"api_key": "${env:OPENAI_API_KEY|}"
}
}
```
### Connections [#connections]
The connections section defines how data flows between nodes. Each connection specifies:
* **Source node**: Identified by `extension_group` and `extension` properties
* **Data protocols**: Types of data that can flow: `audio_frame`, `video_frame`, `data`, or `cmd`
* **Destinations**: Where each data type is routed
For each protocol, you define destinations with:
* `name`: The data property key
* `dest`: List of target nodes
The following example connects `agora_rtc` extension to `deepgram_asr` extension. The `agora_rtc` extension sends `pcm_frame` data to `deepgram_asr` extension.
```json
{
"extension_group": "default",
"extension": "agora_rtc",
"audio_frame": [
{
"name": "pcm_frame",
"dest": [
{
"extension_group": "default",
"extension": "deepgram_asr"
}
]
}
]
}
```
## Extension folder [#extension-folder]
The `ten_packages/extension` folder contains extension modules written in Python, Go, or C++. Each extension is a self-contained package with its own structure.
Extension folder names typically match the module name, though the definitive module name is specified in the extension's `manifest.json` file. Use this module name as the `addon` property value in your configuration.
The extension folder is structured as follows:
```text
project_root/
├── ten_packages/
│ └── extension/
│ ├── sample_python_extension/
│ │ ├── ...
│ │ ├── extension.py
│ │ ├── manifest.json
│ │ └── property.json
│ ├── sample_go_extension/
│ │ ├── ...
│ │ ├── example_extension.go
│ │ ├── manifest.json
│ │ └── property.json
│ └── sample_cpp_extension/
│ ├── ...
│ ├── src/
│ │ ├── main.cc
│ │ └── ...
│ ├── BUILD.gn
│ ├── manifest.json
│ └── property.json
├── manifest.json
└── property.json
```
### Extension common files [#extension-common-files]
* `manifest.json`: Defines the extension's metadata including name, version, available properties, and supported APIs such as `data`, `audio_frame`, and `video_frame`
* `property.json`: Specifies default configuration values for the extension
### Extension-specific files [#extension-specific-files]
Each language has its own set of required files for extension development:
| Language | Main logic file | Dependencies/Build file |
| -------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| Python | `extension.py` | `requirements.txt`: Python dependencies required by the extension. Dependencies are installed automatically when you run `task use`. |
| Go | `example_extension.go` | `go.mod`: Go module definition that specifies the module name and dependencies. |
| C++ | `src/main.cc` | `BUILD.gn`: Build configuration that specifies the target name and dependencies. |
The main logic file contains the core implementation of the extension for each language.
## Web server [#web-server]
The Go-based web server manages agent processes and handles HTTP requests from clients. Both the playground and demo interfaces communicate with this server, though you can also interact directly using curl or any HTTP client.
### API endpoints [#api-endpoints]
The server exposes three endpoints for agent management:
* [POST /start](#post-start): Start an agent with specified configuration
* [POST /stop](#post-stop): Stop a running agent
* [POST /ping](#post-ping): Maintain agent connection
#### POST /start [#post-start]
Starts an agent with a specified graph and property overrides. The agent joins the specified RTC channel and subscribes to the user's audio stream.
| Parameter | Description |
| -------------- | --------------------------------------------------------------------------------------------------------------------- |
| `request_id` | UUID for request tracking |
| `channel_name` | RTC channel name (must match the channel your client joins) |
| `user_uid` | User's RTC identifier for audio subscription |
| `bot_uid` | Optional agent RTC identifier |
| `graph_name` | Graph configuration name from `property.json` |
| `properties` | Property overrides (temporary, doesn't modify `property.json`) |
| `timeout` | Inactivity timeout in seconds (`-1` disables timeout, default: 60s, configurable using `WORKER_QUIT_TIMEOUT_SECONDS`) |
Example:
```bash
curl 'http://localhost:8080/start' \
-H 'Content-Type: application/json' \
--data-raw '{
"request_id": "c1912182-924c-4d15-a8bb-85063343077c",
"channel_name": "test",
"user_uid": 176573,
"graph_name": "camera_va_openai_azure",
"properties": {
"openai_chatgpt": {
"model": "gpt-4o"
}
}
}'
```
#### POST /stop [#post-stop]
Stops a running agent instance.
| Parameter | Description |
| -------------- | ----------------------------------------- |
| `request_id` | UUID for request tracking |
| `channel_name` | Channel name used when starting the agent |
Example:
```bash
curl 'http://localhost:8080/stop' \
-H 'Content-Type: application/json' \
--data-raw '{
"request_id": "c1912182-924c-4d15-a8bb-85063343077c",
"channel_name": "test"
}'
```
#### POST /ping [#post-ping]
Maintains the agent connection by sending a keepalive signal. Not required if the agent was started with `timeout: -1`. Without pings, agents terminate after the configured timeout period.
| Param | Description |
| -------------- | -------------------------------- |
| `request_id` | UUID for request tracking |
| `channel_name` | Channel name of the active agent |
Example:
```bash
curl 'http://localhost:8080/ping' \
-H 'Content-Type: application/json' \
--data-raw '{
"request_id": "c1912182-924c-4d15-a8bb-85063343077c",
"channel_name": "test"
}'
```
# Studio overview (/en/ai/studio)
Agent Studio is a visual workspace in the Agora Console for building and operating conversational AI voice agents. You configure agent behavior, connect telephony, and monitor production calls from a single interface, without writing integration code.
Studio abstracts the underlying AI stack. You configure your ASR, LLM, and TTS providers; Studio handles the orchestration between them. Instead of managing API integrations across multiple services, you focus on what your agent should say and do.
## How Studio is organized [#how-studio-is-organized]
Studio is structured around three areas that map to the agent lifecycle:
**Build:** Create and configure agents. Start from a use-case template or a blank template, then define the system prompt, greeting, voice, and behavior. Connect credentials, knowledge bases, and MCP servers through the **Integration** interface. Studio stores these resources centrally so they can be reused across multiple agents. See [Customize your agent](build/customize-agent) and [Manage integrations](build/integrations).
**Deploy:** Connect agents to telephony. Assign phone numbers using Elastic SIP Trunk and configure inbound routing or outbound campaigns. Studio supports providers such as Twilio, Exotel, and Telnyx. See [Publish your agent](deploy/deploy-agent), [Phone Numbers](deploy/import), [Handle inbound calls](deploy/inbound), and [Campaigns](deploy/campaign).
**Observe:** Monitor production usage. Review call history, transcripts, and analytics to understand agent performance and debug issues. See [Analytics](observe/analytics) and [Call History](observe/call-history).
## How it works [#how-it-works]
Studio sits between your telephony provider and the AI models that power your agent. For inbound calls, Studio receives the call from your carrier via Elastic SIP Trunk, processes the conversation through your configured ASR, LLM, and TTS providers, and delivers the agent's response back to the caller. For outbound calls, Studio initiates the call through the same SIP trunk, connects to the recipient, and handles the conversation in the same way.
In both cases, Studio manages the full call lifecycle. No SIP servers to manage, no voice recognition to train, no telephony protocols to debug.
## Key concepts [#key-concepts]
### Agents [#agents]
Agents are the primary objects in Studio. You create an agent from a template or from scratch, then configure it across the following areas:
* **Live Audio Input**: Voice Activity Detection (VAD), turn detection, and interruption mode
* **LLM**: The provider and the prompt that defines conversational behavior
* **ASR**: The speech recognition provider and language settings
* **TTS**: The voice synthesis provider and voice parameters
After configuring, test the agent in the built-in playground and activate it when ready.
### Integration [#integration]
The **Integration** section provides a centralized resource library for your Studio workspace. Store API credentials, knowledge bases, and MCP server connections here once, then attach them to any agent. This eliminates repeated setup when you create new agents or update provider settings.
### Phone numbers [#phone-numbers]
Import phone numbers from your SIP trunk provider and assign them to agents for inbound calls or use them as caller IDs for outbound campaigns.
### Campaigns [#campaigns]
For outbound use cases, create campaigns with contact lists and scheduling. See [Set up a campaign](deploy/campaign).
## Who should use Studio [#who-should-use-studio]
Studio is designed for developers and technical users who want to build and operate voice agents without managing the underlying AI infrastructure. If you need capabilities beyond what Studio exposes, such as advanced API parameters or custom orchestration logic, you can use the [Conversational AI REST API](/en/api-reference/api-ref/conversational-ai) for full programmatic control.
## Get started [#get-started]
To get started, follow the [Quickstart](quickstart), which walks you through creating and testing your first agent in about 10 minutes using a pre-built template.
# Quickstart (/en/ai/studio/quickstart)
This page guides you through creating and testing your first AI voice agent using Agent Studio. In less than 10 minutes, you will have a working agent that you can speak to in the browser. No telephony setup is required.
## Prerequisites [#prerequisites]
Make sure you have the following:
* An active Agora account and project
* [Enabled Conversational AI](../reference/enable-conversational-ai) for your Agora project
## Create an agent [#create-an-agent]
To create your first agent:
1. Log into [Agora Console](https://console.agora.io) and open [Agent Studio](https://console.agora.io/studio).

2. Select **Agents** from the sidebar, then click **Create Agent**.
3. Select an Agora project from the dropdown. The agent uses this project's credentials to connect to Agora, and usage is billed to this project.

4. Under **Choose a template**, select the **Blank Template**.
5. Enter a name for your agent.
6. Click **Create Agent**.
Studio opens the agent editor with a system prompt and pre-configured model settings.

## Configure your agent [#configure-your-agent]
The agent editor is organized into tabs. For the quickstart, you only need to review the **Prompt** tab and the **Models** tab.
### Review the prompt [#review-the-prompt]
The agent editor opens with the following prompts:
1. **System prompt**: It defines how your agent behaves.
2. **Greeting message**: It is the first thing your agent says when a session starts. Type in a greeting such as `Hello, how can I help you today?`
3. **Failure message**: It is used when the LLM encounters an error or fails to respond.
### Review selected models [#review-selected-models]
Models determine which ASR, LLM, and TTS services your agent uses to process speech, generate responses, and synthesize voice output.
1. Click the **Models** tab.

2. Review the pre-selected models for **Automatic Speech Recognition (ASR)**, **Large Language Model (LLM)**, and **Text-to-Speech (TTS)**. You can use the defaults or select a different vendor-model from the dropdowns.
The dropdowns list vendor-model combinations that support Agora Managed Key. When you use a managed key, Agora provides the API credentials for your selected vendor. You do not need to obtain an API key directly from the vendor. See [Pricing](../reference/pricing) for details. To use your own API key or use a vendor that does not support a managed key, see [Customize your agent](build/customize-agent).
## Test the agent [#test-the-agent]
1. In the right panel, click **Start Call** to start a test call.
2. Allow microphone access when prompted.
3. Speak to the agent to verify it responds correctly.
For guidance on what to test and how to troubleshoot issues, see [Test your agent](build/test-agent).
## Next steps [#next-steps]
You now have a working voice agent. From here you can:
* [Customize your agent](build/customize-agent): Fully configure models, prompts, and advanced settings for your use case
* [Manage integrations](build/integrations): Manage API credentials, knowledge bases, and MCP servers for reuse across agents
# Account settlement (/en/introduction/billing/account-settlement)
To ensure billing transparency and smooth service continuity, Agora implements real-time account balance reservation based on estimated usage across all Agora products for SSP customers. This does not apply to customers with a signed contract.
For uninterrupted access to Agora's products and services, ensure at least one of the following:
* Add a valid credit card for auto-recharge to your Agora account, **or**
* Maintain sufficient funds in your Agora Console balance to cover your estimated usage.
Agora employs the following policies to manage your account balance and ensure uninterrupted access to its products and services.
### Real-time usage and estimated bill [#real-time-usage-and-estimated-bill]
You can view your real-time usage and estimated monthly bill at any time directly in the Agora Console.

### Real-time reservation of balance [#real-time-reservation-of-balance]
Your reserved balance is updated continuously based on your real-time bill estimation. The corresponding estimated amount is reserved from your available balance at all times.

### Auto-recharge and account suspension [#auto-recharge-and-account-suspension]
On the **7th, 14th, 21st, and 28th** of each calendar month (excluding February 28), if your available balance is in arrears by more than **$50**:
* If a valid credit card is linked, the system automatically recharges your balance to zero.
* If no credit card is linked, or if auto-recharge fails, a notification email is sent to you.
* Top up your wallet within the **24-hour grace period** to avoid account suspension.
* If your balance remains in arrears after 24 hours, your account is suspended until the balance is restored to zero or above.
### Monthly billing [#monthly-billing]
Your final bill is issued monthly. After bill finalization:
* Any overpaid amount is refunded to your balance.
* Any undercharged amount is deducted accordingly.
### Balance withdrawal [#balance-withdrawal]
You can withdraw your available balance for payments completed within the past 90 days directly through the Agora Console. Funds will be returned to the original payment method. For further inquiries, please email [billing@agora.io](mailto\:billing@agora.io).

# Billing policies (/en/introduction/billing/billing-policies)
This page explains billing, account settlement, end-of-life policies, and any applicable free-of-charge policies for this product.
## Billing and account policies [#billing-and-account-policies]
Agora Console provides billing information, fee deduction details, and account suspension notices based on your account type.
If you have signed a contract with Agora, the contract terms override all billing, deduction, and suspension details described on this page.
### Paid accounts [#paid-accounts]
Your account is a paid account if you have registered with Agora and completed any of the following:
* Added a credit card to your account or topped up your balance using a bank account.
* Made a recent payment.
* Signed a contract with Agora.
### Billing cycle [#billing-cycle]
Agora provides each account with [10,000 free minutes](#free-of-charge-policy) every month.
On the first day of each month, Agora issues your bill for the previous calendar month.
To view billing information for your projects:
1. In [Agora Console](https://console.agora.io/v2), click home.
2. Click **Billing**.
You see the detailed billing information for your projects, including billing period, due date, and amount.

#### Additional charges [#additional-charges]
This section describes the additional charges applicable to your account.
#### Singapore goods and services tax [#singapore-goods-and-services-tax]
As of November 2021, Agora Singapore charges [9% Singapore Goods and Services Tax (GST)](https://www.iras.gov.sg/taxes/goods-services-tax-\(gst\)/basics-of-gst/goods-and-services-tax-\(gst\)-what-it-is-and-how-it-works) on invoices for all Agora services provided to accounts located in Singapore. Agora determines the account location based on the tax identification number, contact address, or billing address that you have provided. All GST collected from Singapore accounts is paid to the Singapore tax authority.
If you have any questions, contact [support@agora.io](mailto\:support@agora.io).
#### Fee deduction [#fee-deduction]
On the sixth day of each month, Agora automatically deducts the fee for the previous month and notifies you by email. No fee is deducted if you meet both of the following conditions:
* Your monthly usage does not exceed the free quota.
* You do not use any other charged Agora services or products.
If your account balance is negative after the deduction, Agora sends you an email, reminding you to top up your account at your earliest convenience and avoid account suspension.
#### Account suspension [#account-suspension]
If your account balance remains negative for 5 days after the deduction date, Agora suspends your account and notifies you by email. During suspension, none of your projects can access Agora services.
To restore access, top up your account as soon as possible. Once your balance is zero or greater, Agora unfreezes your account.
### Free accounts [#free-accounts]
Your account is a free account if you have registered with Agora and have not completed any of the following:
* Added a credit card to your account or topped up your balance using a bank account.
* Made a recent payment.
* Signed a contract with Agora.
Free accounts include 10,000 free minutes per month. For details, see [Free-of-charge policy](#free-of-charge-policy).
#### Free account suspension [#free-account-suspension]
Agora suspends your account on the second day after any of the following occur:
* Your total usage exceeds the free quota of a service or product.
* You use Agora services or products not included in the free quota.
After account suspension, none of your projects can access Agora services. To restore access:
1. Add a credit card to your account or top up your balance using a bank account. This unfreezes your account and upgrades it to a paid account.
2. Purchase a [pre-paid monthly package](/en/introduction/billing/subscription-packages) or [top-up package](/en/introduction/billing/subscription-packages#top-up-package). This option is highly recommended.
## Free-of-charge policy [#free-of-charge-policy]
Agora offers a new pricing model. If your account uses this model, you receive a monthly free usage package by default instead of the 10,000 free minutes described below.
Agora provides each account with 10,000 free minutes per month and deducts them in the following order:
* Broadcast Streaming audio minutes
* Voice call, video call, and Interactive Live Streaming audio minutes
* On-premise recording audio minutes
* Cloud recording audio non-transcoding minutes
* Cloud recording audio minutes
* Web page recording audio minutes
* Cloud Proxy audio minutes
* Broadcast Streaming HD video minutes
* Voice call, video call, and Interactive Live Streaming HD video minutes
* On-premise recording HD video minutes
* Cloud recording HD video minutes
* Web page recording HD video minutes
* Cloud Proxy HD video minutes
* Broadcast Streaming Full HD\* video minutes
* Voice call, video call, and Interactive Live Streaming Full HD\* video minutes
* On-premise recording Full HD\* video minutes
* Cloud recording Full HD\* video minutes
* Web page recording Full HD\* video minutes
* Cloud Proxy Full HD\* video minutes
* Broadcast Streaming 2K video minutes
* Voice call, video call, and Interactive Live Streaming 2K video minutes
* On-premise recording 2K video minutes
* Cloud recording 2K video minutes
* Broadcast Streaming 2K+ video minutes
* Voice call, video call, Interactive Live Streaming 2K+ video minutes
* On-premise recording 2K+ video minutes
* Cloud recording 2K+ video minutes
* Cloud proxy 2K+ video minutes
\* HD+ minutes are also included in the shared 10,000 free minutes.
If your total service minutes do not exceed 10,000 minutes, the service is free-of-charge. After the 10,000 free-of-charge minutes are fully deducted, Agora charges you for the additional service minutes. Agora clears any remaining free-of-charge minutes at the end of each calendar month.
The 10,000 free-of-charge minutes policy does not apply to the Signaling SDK, the Chat SDK, or the IoT SDK.
### How service minutes are calculated [#how-service-minutes-are-calculated]
Service minutes are calculated either by the number of users or by the number of streams. Agora calculates service minutes **by the number of users**.
#### Approach 1: calculate by the number of users [#approach-1-calculate-by-the-number-of-users]
Suppose N users talk for M minutes in a channel, the total service minutes = N \* M.
* If two users talk for 10 minutes, the total service minutes are: 2 \* 10 = 20.
* If five users talk for 10 minutes, the total service minutes are: 5 \* 10 = 50.
* If 10 users talk for 10 minutes, the total service minutes are: 10 \* 10 = 100.
In this approach, service minutes depend only on the number of users in the channel, regardless of how many streams each user subscribes to.
#### Approach 2: calculate by the number of streams [#approach-2-calculate-by-the-number-of-streams]
Suppose N users talk for M minutes in a channel, and each user subscribes to all remote streams in the channel, the total service minutes = N \* (N-1) \* M.
* If two users talk for 10 minutes, the total service minutes are: 2 \* (2-1) \* 10 = 20.
* If five users talk for 10 minutes, the total service minutes are: 5 \* (5-1) \* 10 = 200.
* If 10 users talk for 10 minutes, the total service minutes are: 10 \* (10-1) \* 10 = 900.
In this approach, every remote stream that a user subscribes to is counted separately.
#### Comparison of calculation approaches [#comparison-of-calculation-approaches]
The following table compares service minutes under each approach:
| Use-case | Service minutes by the number of users | Service minutes by the number of streams |
| ------------------------------- | -------------------------------------- | ---------------------------------------- |
| Two users talk for 10 minutes. | 20 minutes | 20 minutes |
| Five users talk for 10 minutes. | 50 minutes | 200 minutes |
| 10 users talk for 10 minutes. | 100 minutes | 900 minutes |
The difference between the two approaches increases as more users join the channel.
### Agora's calculation method [#agoras-calculation-method]
Agora calculates service minutes **by the number of users**. Service minutes are also calculated based on aggregate video resolution.
## End-of-life policy [#end-of-life-policy]
Agora is committed to providing regular updates to core products, extensions, and tools. These updates include new features, updated APIs, bug fixes, security patches, and documentation improvements. Agora strongly encourages developers to update to the latest product releases to benefit from new features, security enhancements, and other improvements.
This section outlines the stages that an Agora product or service moves through, from pre-GA Beta to general availability to retirement, and the support Agora provides during each phase.
### Default introduction period [#default-introduction-period]
Agora guarantees a minimum introduction period of 12 months from the release of a product on the Agora Developer Center. During this period, Agora does not initiate any end-of-support or end-of-life actions. After the 12-month period, Agora may transition a product into the Maintenance phase, which marks the beginning of the end-of-support phase.
### SDK lifecycle [#sdk-lifecycle]
| Phase | Description |
| ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Beta | During this phase, SDKs are intended solely for early access and feedback purposes. They are not recommended for use in production environments. Beta products fall outside of the EOL policy and are intended for evaluation purposes only. For more information, see the Beta Service Agreement. |
| General Availability (GA) | SDKs in this phase are fully supported by Agora. Agora provides support for new services, API updates, feature enhancements, bug fixes, and security patches. Agora guarantees a minimum of 12 months of support for GA SDKs. For details on client SDK support periods, see [Client SDK support periods](#client-sdk-support-periods). |
| Retirement (EOL) SDKs: (Maintenance) | When an SDK enters the Retirement phase, Agora announces its retirement with a minimum notice period of 60 days, which is approximately 2 months. This announcement will include crucial timelines and guidelines to assist developers in migrating to the latest recommended SDK version. Agora communicates retirement announcements through email, the Agora Console notifications center, and SDK documentation, and may also post announcements on social media or the [Agora blog](https://www.agora.io/en/blog/). When a product or service reaches the end-of-support or end-of-life stage, Agora stops providing security updates, non-security updates, and assisted support. |
### Client SDK support periods [#client-sdk-support-periods]
Agora provides the following support periods for client SDKs:
| Release | Support |
| ------------- | ------------------------------------------------------------------------------------------ |
| Major release | Agora guarantees support for at least 12 months starting from the date of a major release. |
| Minor release | For minor releases, Agora ensures support for at least 6 months from the release date. |
### Maintenance support for retired SDKs [#maintenance-support-for-retired-sdks]
Occasionally, Agora may transition a product SDK into a maintenance support phase focused primarily on bug fixes and security patches. In such cases, Agora notifies customers at least 60 days, approximately 2 months, in advance of the retirement period for the affected SDK.
During the Retirement phase, products entering the EOS/EOL phase will continue to be supported as follows:
| Duration | Support |
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| Months 1–6 after EOL announcement | Agora provides bug fixes and security updates. New feature requests are not accepted. |
| Months 7–12 after EOL announcement | Agora provides security updates only. |
| After 12 months | Agora no longer supports the product or service, unless a support extension was arranged before the end of the 12-month period. |
Using an SDK beyond its maintenance support cycle is not recommended and is entirely at the developer's discretion and risk.
Agora is committed to delivering the best SDK experience to its developers. This policy ensures that you have access to the latest features and security updates with clear guidance on the support timelines.
### Support extensions [#support-extensions]
Customers may apply to extend support for a retired SDK for a temporary period, subject to Agora's discretion and commercial terms. Agora reviews each request and decides whether to grant the customer a license to use the product or service for a limited period beyond the EOL timeline.
# Billing (/en/introduction/billing)
This is the single place to understand how Agora charges for usage and how to manage payment. For product-specific rates, see the **Pricing** page of each product.
## Billing center [#billing-center]
In Agora Console, the **Billing** page lets you check your balance, add funds, and view or export invoices and transactions. Access is limited to members assigned to **Admin**, **Finance**, or an authorized custom team.
## How usage is billed [#how-usage-is-billed]
Most Agora products bill by usage (for example, by minutes, message volume, storage, or peak concurrent users), with a free tier before charges begin. The exact unit and rate depend on the product, so check the product's own **Pricing** page for current numbers.
Before you ship to production, confirm:
* the billing unit makes sense for your expected usage pattern
* the right people on your team can access billing and financial records
## Billing policies and management [#billing-policies-and-management]
* [Billing policies](/en/introduction/billing/billing-policies) — free-usage allowance and product lifecycle rules.
* [Account settlement](/en/introduction/billing/account-settlement) — balance reservation, auto-recharge, and suspension.
* [Subscription packages](/en/introduction/billing/subscription-packages) — package tiers and upgrades.
# Subscription packages (/en/introduction/billing/subscription-packages)
A subscription package is a prepaid billing method. You can purchase a package in the [`Agora Console`](https://console.agora.io/) to cover usage for the current month.
This page explains how to purchase, and manage subscription and top-up packages.
### Purchase and upgrade [#purchase-and-upgrade]
By default, a new account may be assigned a Free package when the first eligible project is created. You can upgrade to a paid package at any time. Available package tiers, discounts, and unit prices vary by product. To upgrade:
* Log in to [Agora Console](https://console.agora.io/).
* In the sidebar, click **Subscriptions**.
* Under **All Subscriptions**, select the product subscription you want to manage, such as RTC, Signaling, Chat, or Agora Analytics.
* Select your package and click **Subscribe** or **Upgrade**.
The following screenshot shows an RTC subscription package as an example:

- For non-contracted customers, most packages can be purchased directly from the Agora Console. Some enterprise or contracted packages may require contacting [Agora sales](mailto\:sales@agora.io).
- You can upgrade sequentially or skip levels when the product supports it.
- Package upgrades take effect immediately and apply to the entire calendar month. If you make multiple upgrades on the same day, only the last upgrade is applied.
- If your account has a negative balance, purchasing a prepaid package first charges your credit card for the outstanding balance, then for the package fee.
#### Upgrade rules [#upgrade-rules]
When you upgrade to a paid package, it becomes effective for the entire calendar month, regardless of the purchase date. You receive the full monthly allocation of the included quota. The cost of the previous package is deducted from the new package price, so you only pay the difference. If you request an upgrade after auto-renewal has triggered, you may upgrade for the next month as well.
### Renewal [#renewal]
By default, your subscription package renews automatically 48 hours before it expires. Ensure your account has sufficient balance or enable auto-pay to avoid renewal failure. You can unsubscribe at any time to prevent renewal. See [Cancel subscription](#cancel-subscription).
If renewal fails:
* The system sends a notification and retries the renewal.
* If renewal still fails by the time the package expires, the package downgrades to the Free package.
* To restore the previous package, see [Purchase and Upgrade](#purchase-and-upgrade).
### Downgrade [#downgrade]
You can downgrade by selecting and purchasing a lower-tier package. A downgrade takes effect when your current package expires if you request it before auto-renewal is triggered. You are charged for the new package when you request the downgrade.
You can downgrade only once per month.
* If your downgrade for the next month is successful, auto-renewal for the current package is not triggered.
* If a package is already active, you must wait until the 1st day of that month to initiate a downgrade for the following month.
#### Example [#example]
* **Requested more than 48 hours before the current package expires**: The downgrade takes effect after the current package expires.
* **Requested less than 48 hours before the current package expires**: Since a package is already active for the next month, you must wait until the new package starts to downgrade for the following month.
### Cancel subscription [#cancel-subscription]
To cancel a prepaid package, downgrade to the Free package. Downgrade rules apply.
## Top-up package [#top-up-package]
A top-up package is a pay-as-you-go plan. You can purchase a top-up package from the [`Agora Console`](https://console.agora.io/subscriptions/rtc-plans?tab=top-up) to cover usage that exceeds your monthly package quota.
### Purchase [#purchase]
You can purchase multiple top-up packages. They are deducted in the order of purchase.
#### Example: [#example-1]
* Purchased 250,000-minutes top-up packages on **August 15** and **August 16**. On **September 1**, minutes consumed in August are deducted from the first package, then from the second.
### Validity [#validity]
Each top-up package is valid for one year, starting from the purchase date to the end of the same month of the following year.
#### Example: [#example-2]
* Purchased on **August 15, 2025** → valid until **August 31, 2026**
Unused minutes expire after the validity period and cannot be carried forward.
### Cancel top-up package [#cancel-top-up-package]
Cancelling a top-up package is not supported.
## Free tier overage [#free-tier-overage]
For Free tier users, exceeding the included minutes and any purchased top-up minutes results in service suspension. Service resumes when you purchase additional top-up minutes or upgrade to a prepaid subscription package.
# Start building (/en/introduction/get-started/build-it-yourself)
Most teams do not start by asking which Agora product to read first. They start by asking what kind of realtime problem they need to solve.
Use this page to map your product goal to the most useful documentation path.
## When to use this path [#when-to-use-this-path]
This is the right page when:
* you already know the product problem you want to solve
* you want to choose the right capability family before reading deep product docs
* you do not need an AI-assisted setup workflow first
* you want the shortest route from problem statement to relevant quickstart or product area
If you want an AI agent to help choose the route and bootstrap a starter, use [Start with AI](/en/introduction/start-with-ai) instead.
## Start from the problem you are solving [#start-from-the-problem-you-are-solving]
### Voice-first AI interaction [#voice-first-ai-interaction]
If users need to talk to an AI agent in real time, start with:
* [Conversational AI](/en/introduction/conversational-ai)
* [AI quickstart](/en/ai/get-started/quickstart)
* [Voice agent quickstart](/en/ai/choose-your-path/quickstart-coding)
This path is usually right for assistants, tutors, companions, service agents, and voice-enabled devices.
### Live voice, video, or interactive sessions [#live-voice-video-or-interactive-sessions]
If users need to join the same session and interact through live media, start with:
* [Real-Time Voice & Video](/en/introduction/realtime-audio-video)
* [Audio & Video](/en/realtime-media/rtc)
* [Voice & Video quickstart](/en/realtime-media/rtc/android/quick-start/build-from-scratch)
This path is usually right for calling, meetings, classrooms, social rooms, telehealth, and interactive livestreaming.
### Room messaging, state sync, or workflow coordination [#room-messaging-state-sync-or-workflow-coordination]
If the product depends on chat, presence, metadata, or event coordination, start with:
* [Messaging & Presence](/en/introduction/messaging-presence)
* [RTM / Signaling](/en/realtime-media/rtm)
* [IM / Chat](/en/realtime-media/im)
This path is usually right for in-room communication, control flows, collaborative state, and backend-triggered room actions.
### Cloud-side media workflows [#cloud-side-media-workflows]
If the media must be captured, processed, routed, or redistributed beyond the live session itself, start with:
* [Cloud Media Services](/en/introduction/cloud-media-services)
* [Cloud Recording](/en/realtime-media/cloud-recording)
* [Realtime Transcription & Translation](/en/realtime-media/speech-to-text)
* [Transcoding](/en/realtime-media/transcoding)
* [Server Media Processing](/en/realtime-media/rtc-server-sdk)
This path is usually right for archive, replay, moderation, server-side automation, and downstream content pipelines.
## Common combinations [#common-combinations]
Many production systems combine several of these paths:
* voice or video sessions plus [Messaging & Presence](/en/introduction/messaging-presence)
* AI interaction plus [Cloud Media Services](/en/introduction/cloud-media-services)
* live sessions plus [Cloud Media Services](/en/introduction/cloud-media-services)
* broadcast products plus [Cloud Media Services](/en/introduction/cloud-media-services)
## What to do next [#what-to-do-next]
If you are still evaluating the platform, read [About Agora](/en/introduction/about-agora) first. If you want the shared mental model before choosing a path, read [Core Concepts](/en/introduction/core-concepts). If you want an AI-assisted setup workflow, switch to [Start with AI](/en/introduction/start-with-ai).
# Service activation (/en/realtime-media/agora-analytics/activation)
To activate Agora Analytics, follow these steps:
1. Contact your account executive, solutions architect, or email [support@agora.io](mailto\:support@agora.io) to learn about the available Agora Analytics plans.
2. The account team or your solutions architect will open a support ticket with [Technical support](mailto\:support@agora.io) to request activation. Make sure the ticket includes your Customer ID and the specific Agora Analytics plan you want activated.
Activation typically takes up to 2 business days.
# Agora Analytics overview (/en/realtime-media/agora-analytics/product-overview)
Agora Analytics tracks and analyzes the usage, quality, and performance of real-time voice and video live streams and other Agora products. Designed as a companion to Agora's Voice Calling, Video Calling, Broadcast Streaming, Interactive Live Streaming, and Chat it helps locate quality issues, identify root causes, and resolve problems to enhance the end-user experience. Monitor sessions in real-time with Call Inspector or use RESTful APIs to analyze usage trends and performance.
Extend Agora Analytics with the Datadog integration to bring Agora Analytics data into your Datadog account. Visualize and monitor metrics, set up custom alerts, and manage Agora data alongside other system metrics for a unified view of performance and quality.
## Start building [#start-building]
## Product Features [#product-features]
Identify, analyze, and respond to performance quality issues with advanced search, detailed visibility into call and user metrics, and diagnostics.
Provides periodic call usage and quality statistics. It is designed to help customers understand the usage and quality of calls in apps.
Visualized data for multiple call metrics in real time.
Sends alerts to you when abnormal metrics or events are detected in your Agora projects.
Use RESTful APIs to retrieve call statistics and quality of experience metrics, use them in your own application or DataOps workflow.
Easily embed Agora Analytics pages in internal web portals using a low-code approach.
# Core concepts (/en/realtime-media/broadcast-streaming/core-concepts)
RTC (Real-Time Communication) refers to real-time communication technology, which allows almost instant exchange of audio, video, and other data between the sender and the receiver.
Agora SDKs provide real-time audio and video interaction services, with multi-platform and multi-device support. This includes high-definition video calls, voice-only calls, interactive live streaming, as well as one-on-one and multi-group chats.
This guide introduces the key processes and concepts you need to know to use Video SDK.
Agora relies on the following fundamental concepts to enable seamless real-time communication:
### Agora SDRTN® [#agora-sdrtn]
Agora's core engagement services are powered by its Software-Defined Real-Time Network (SDRTN®), a global infrastructure accessible anytime, anywhere. Unlike traditional networks, Agora SDRTN® is not restricted by devices, phone numbers, or telecom coverage areas. With data centers in over 200 countries and regions, it ensures sub-second latency and high availability for real-time media.
Agora SDRTN® enables live user engagement through real-time communication (RTC), offering:
* Unmatched quality of service
* High availability and accessibility
* True scalability
* Low cost
## Channel concepts [#channel-concepts]
Agora uses channels to group users together, enabling seamless communication and interaction. Channels serve as the foundation for transmitting real-time data, whether audio, video, or signaling, and play a crucial role in connecting users and services.
### Channel [#channel]
A channel organizes users into a group and is identified by a unique channel name. Users who connect to the same channel are able to communicate with each other. A channel is created when the first user joins and ceases to exist when the last user leaves.
Channels are created by calling the methods for transmitting real-time data. Agora uses different channels to transmit different types of data:
* A Video SDK channel is used for transmitting audio or video data.
* A Signaling channel is used for transmitting messaging or signaling data.
These channels are independent of each other.
Additional services provided by Agora, such as Cloud Recording and Speech to Text, join the Video SDK channel to provide real-time recording, transmission acceleration, media playback, and content moderation.
### Channel profile [#channel-profile]
The Video SDK applies different optimization methods according to the selected channel profile. Agora supports the following channel profiles:
| Channel profile | Description |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Communication** | This profile is suitable for one-on-one or group calls, where all users in the channel talk freely. |
| **Live Broadcasting** | In a live streaming channel, users have two client roles: *host* and *audience*. The *host* sends and receives streams, while the *audience* only receives streams with the sending function disabled. |
### Stream [#stream]
A stream is a sequence of digitally encoded, coherent signals that contain media data. Users in a channel [publish](#publish) local streams and [subscribe](#subscribe) to remote streams from other users.
### User role [#user-role]
The user role defines whether a user in a channel has the permission to publish streams. There are two user roles:
* **Host**: A user who can publish streams to a channel.
* **Audience**: A User who can only subscribe to remote media streams. A user with this role cannot publish streams.
### Publish [#publish]
Publishing is the act of sending a user’s audio or video data to the channel. Usually, the published stream is created by the audio data sampled from a microphone or the video data captured by a camera. You can also publish media streams from other sources, such as an online music file or the user’s screen.
After successfully publishing a stream, the SDK uses it to send media data to other users in the channel. Users communicate with each other in real-time by publishing local streams and subscribing to remote streams.
### Subscribe [#subscribe]
Subscribing is the act of receiving media streams published by remote users to the channel. A user receives audio and video data from other users by subscribing to one or more of their streams. You either directly play the subscribed streams or process incoming data for other purposes such as recording or capturing screenshots.
### User ID [#user-id]
In Broadcast Streaming, the UID is an integer value that uniquely identifies a user within the context of a channel. When joining a channel, you have the option to either assign a specific UID to the user or pass `0` or `null` and allow Agora to automatically generate and assign a UID to the user. If two users attempt to join the same channel with the same UID, it can lead to unexpected behavior.
The UID is used by Agora's services and components to identify and manage users within a channel. Ensure that UIDs are properly assigned to prevent conflicts.
### RTC connection [#rtc-connection]
The connection between the SDK and the channel. When publishing or subscribing to multiple streams in multiple channels, a connection is used to specify the target channel.
## Credentials [#credentials]
To ensure reliable access and secure communication, Agora uses credentials such as the App ID, App Certificate, and tokens to identify applications, authenticate their requests, and authorize their access on its platform.
### App ID [#app-id]
The App ID is a unique key generated by Agora to identify each project and provide billing and other statistical data services. The App ID is critical for connecting users within your app. It is used to initialize the Agora Engine in your app, and as one of the required keys to create authentication tokens for secure communication. Retrieve the App ID for your project using the [Agora Console](https://console.agora.io/v2/project-management).
App IDs are stored on the front-end client and do not provide access control. Projects using only an App ID allow any user with the App ID to join. For access control, especially in production environments, choose the **App ID + Token** mechanism for user authentication when creating a new project. Without authentication tokens, your environment is open to anyone with access to your App ID.
### App Certificate [#app-certificate]
An App Certificate is a unique key generated by the Agora Console to secure projects through token authentication. It is required, along with the App ID, to generate a token that proves authorization between your systems and Agora's network. App Certificates are used to generate Broadcast Streaming authentication tokens.
Store the App Certificate securely in your backend systems. If your App Certificate is compromised or to meet security compliance requirements, you can invalidate certificates and create new ones through the Agora Console.
### Tokens [#tokens]
A token is a dynamic key generated using the App ID, App Certificate, user ID, and expiration timestamp. Tokens authenticate and secure access to Agora's services, ensuring only authorized users can join a channel and participate in real-time communication.
Tokens are generated on your server and passed to the client for use in Broadcast Streaming. The token generation process involves digitally signing the App ID, App Certificate, user ID, and expiration timestamp using a specific algorithm, preventing tampering or forgery.
During development and testing, use the Agora Console to generate temporary tokens. For production environments, implement a token server as part of your security infrastructure to control access to your channels.
### Agora Console [#agora-console]
[Agora Console](https://console.agora.io/v2) is the main dashboard where you manage your Agora projects and services. Before you can use Agora's SDKs, you must first create a project in the Agora Console. See [Agora account management](manage-agora-account) for
details.

Agora Console provides an intuitive interface for developers to query and manage their Agora account. After registering an Agora account, you use the Agora Console to perform the following tasks:
* Manage your account
* Create and configure Agora projects and services
* Get an App ID and the App certificate
* Generate temporary tokens for development and testing
* Manage members and roles
* Check call quality and usage
* Check bills and make payments
* Access product resources
See [Agora account management](manage-agora-account) for details on how to manage all aspects of your Agora account.
Agora also provides RESTful APIs that you use to implement features such as creating a project and fetching usage numbers programmatically.
## Audio and video concepts [#audio-and-video-concepts]
### Audio and video interaction workflow [#audio-and-video-interaction-workflow]
The following figure illustrates the workflow of using the Video SDK to implement basic audio and video interaction.

Agora relies on the following fundamental concepts to enable seamless real-time communication:
### Audio module [#audio-module]
In audio interaction, the main functions of the audio module are as shown in the figure below:

After you call `registerAudioFrameObserver`, you can obtain the raw audio data at the following observation points in the audio transmission process:
1. Obtain the raw audio data of ear monitoring through the `onEarMonitoringAudioFrame` callback.
2. Obtain the captured raw audio data through the `onRecordAudioFrame` callback.
3. Obtain the raw audio playback data of each individual stream through the `onPlaybackAudioFrameBeforeMixing` callback.
4. Obtain the raw audio playback data of all mixed streams through the `onPlaybackAudioFrame` callback.
5. Obtain the raw audio data after mixing the captured and playback audio through the `onMixedAudioFrame` callback.
(5) `onMixedAudioFrame` = (2) `onRecordAudioFrame` + (4) `onPlaybackAudioFrame`
### Audio routing [#audio-routing]
The audio output device used by the app when playing audio. Common audio routes include wired headphones, earpieces, speakers, Bluetooth headphones, and others.
The APIs used by the audio module are as follows:
* Enable local audio collection: `enableLocalAudio`
* Set local playback device: `setPlaybackDevice`
* Set up audio routing: `setDefaultAudioRouteToSpeakerphone`
### Video module [#video-module]
The following diagram shows the main functions of the video module in video interaction:

The figure shows the following observation points:
1. `POSITION_POST_CAPTURER_ORIGIN`.
2. `POSITION_POST_CAPTURER`, corresponds to the `onCaptureVideoFrame` callback.
3. `POSITION_PRE_ENCODER`, corresponds to the `onPreEncodeVideoFrame` callback.
4. `POSITION_PRE_RENDERER`, corresponds to the `onRenderVideoFrame` callback.
The APIs used by the video module are as follows:
* Enable local video collection: `enableLocalVideo`
* Local preview: `setupLocalVideo` → `startPreview`
* Video rendering shows: `setupRemoteVideo`
# Broadcast Streaming overview (/en/realtime-media/broadcast-streaming)
Agora's Broadcast Streaming API delivers low-latency, high-definition live video streaming at scale for one-to-many broadcast delivery. With support for cross-platform integration and adaptive streaming technologies, it ensures reliable, high-quality viewing experiences across a wide range of network conditions and devices. Reach global audiences with smooth, uninterrupted broadcasts for large-scale events such as concerts, sports, conferences, live shows, and other live production experiences.
Enhance Agora's Video SDK with broadcast production capabilities such as recording, stream management, content moderation, and audience analytics, or leverage the Extensions Marketplace to enable AI-powered features like noise cancellation, video effects, and more.
## Start building [#start-building]
## Product Features [#product-features]
Agora’s Software-Defined Real-Time Network (SDRTN®) supports video users in over 200 countries and regions.
Consistent high-quality video from few to thousands of concurrent users, even under challenging network conditions.
Publish multiple audio and video tracks to one or more channels from a single instance, with support for multi-channel capture cameras and microphones.
Enable screen sharing or interactive whiteboards that allow users to draw, annotate, and share content from multiple devices simultaneously.
Support for high quality audio with 3D spatial audio, AI noise suppression, and gain control to provide an immersive audio experience.
Record video sessions in the cloud or on premises with control over the format, path of storage, and quality.
# Agora account management (/en/realtime-media/broadcast-streaming/manage-agora-account)
This page shows you how to sign up for an Agora account, create a new project, and get the app ID and app certificate to generate a temporary token.
## Get started with Agora [#get-started-with-agora]
To join a Broadcast Streaming session, you need an Agora App ID. This section shows you how to set up an Agora account, create an Agora project and get the required information from [Agora Console](https://console.agora.io/v2).
### Sign up for an Agora account [#sign-up-for-an-agora-account]
To use Agora products and services, create an Agora account with your email, phone number, or a third-party account.
**Sign up with email**
1. Go to the [signup page](https://sso.agora.io/en/signup).
2. Fill in the required fields.
3. Carefully read the **Terms of Service**, **Privacy Policy**, and **Acceptable Use Policy**, and tick the checkbox.
4. Click **Continue**.
5. Enter your **verification code** and click **Confirm**.
6. Follow the on-screen instructions to provide your name, company name, and phone number, set a password, and click **Continue**.
**Sign up with a third-party account**
1. On the [Agora Console](https://console.agora.io/v2) login page, select the third-party account you want to use.
2. Follow the on-screen instructions to complete verification.
3. Click **Create a new account**.
4. Carefully read the **Terms of Service**, **Privacy Policy**, and **Acceptable Use Policy**, and select the checkbox.
5. Click **Continue**.
Once you sign up successfully, your account is automatically logged in. Follow the on-screen instructions to create your first project and test out real-time communications.
For later visits, log in to [Agora Console](https://console.agora.io/v2) with your phone number, email address, or linked third-party account.
### Create an Agora project [#create-an-agora-project]
To create an Agora project, do the following:
1. In [Agora Console](https://console.agora.io/v2), open the [Projects](https://console.agora.io/v2/project-management) page.
2. Click **Create New**.
3. Follow the on-screen instructions to enter a project name and use case, and check **Secured mode: APP ID + Token (Recommended)** as the authentication mechanism.

4. Click **Submit**. You see the new project on the **Projects** page.
### Get the App ID [#get-the-app-id]
Agora automatically assigns a unique identifier to each project, called an App ID.
To copy this App ID, find your project on the [Projects](https://console.agora.io/v2/project-management) page in Agora Console, and click the copy icon in the **App ID** column.

## Security and authentication [#security-and-authentication]
Use the following features from your Agora account to implement security and authentication features in your apps.
### Get the App Certificate [#get-the-app-certificate]
When generating an authentication token on your app server, you need an App Certificate, in addition to the App ID.
To get an App Certificate, do the following:
1. On the [Projects](https://console.agora.io/v2/project-management) page, click the pencil icon to edit the project you want to use.

2. Click the copy icon under **Primary Certificate**.

### Generate temporary tokens [#generate-temporary-tokens]
To ensure communication security, best practice is to use tokens to authenticate the users who log in from your app.
To generate a temporary RTC token for use in your Video SDK projects:
1. On the [Projects](https://console.agora.io/v2/project-management) page, click the pencil icon next to your project.
2. On the **Security** panel, click **Generate Temp Token**, enter a channel name in the pop-up box and click **Generate**. Copy the generated RTC token for use in your Broadcast Streaming projects.
To generate a token for other Agora products:
1. In your browser, navigate to the [Agora token builder](https://agora-token-generator-demo.vercel.app/).
2. Choose the Agora product your user wants to log in to. Fill in **App ID** and **App Certificate** with the
details of your project in Agora Console.
3. Customize the token for each user. The required fields are visible in the Agora token builder.
4. Click **Generate Token**.
The token appears in Token Builder.
3. Copy the token and use it in your app.
For more information on managing other aspects of your Agora account, see [Agora console overview](reference/console-overview).
# Agora MCP (/en/realtime-media/broadcast-streaming/mcp)
The Agora MCP server gives your AI assistant direct access to Agora's documentation, so it can look up APIs, SDK methods, and platform-specific details in real time.
The Agora MCP server is included when you install Agora Skills. If you prefer to install only the MCP server, it is available at:
```text
https://mcp.agora.io
```
### Installation [#installation]
Refer to the installation instructions for your coding assistant.
Cursor
Claude
Codex
Gemini CLI
Manual installation
Click the button below to install the MCP server in [Cursor](https://www.cursor.com/)
or add it manually with the following JSON:
```json
{
"mcpServers": {
"agora-docs": {
"url": "https://mcp.agora.io"
}
}
}
```
* **Claude Code**
Run the following command in your terminal to install the MCP server in [Claude Code](https://claude.com/product/claude-code):
```bash
claude mcp add --transport http agora-docs https://mcp.agora.io
```
* **Claude Desktop**
In **Settings**, select **Connectors** and then choose **Add custom connector**. Enter the following values and click **Add**:
* **Name**: `agora-docs`
* **Remote MCP server URL**: `https://mcp.agora.io`
Run the following command in your terminal to install the server in [OpenAI Codex](https://openai.com/codex/):
```bash
codex mcp add --url https://mcp.agora.io agora-docs
```
Run the following command in your terminal to install the server in [Gemini CLI](https://github.com/google-gemini/gemini-cli):
```bash
gemini mcp add --transport http agora-docs https://mcp.agora.io
```
Add the server URL `https://mcp.agora.io` to your MCP client of choice. If prompted, set the transport to `http` or "Streamable HTTP".
### Getting started [#getting-started]
Once installed, your coding assistant has access to Agora's documentation through the MCP server. The assistant will intelligently use this resource when relevant to your questions. For more targeted results, mention Agora along with your target product and platform, such as 'iOS', 'Web', 'Conversational AI', 'Video Calling' in your prompts.
### System prompt [#system-prompt]
This MCP works with all LLMs that support MCP, but performs best when the assistant understands facet-based exploration. Add the following prompt to your LLMs custom instructions:
**System prompt for LLMs**
```markdown
# Agora MCP Markdown - System Prompt
You have access to Agora's documentation search via three tools:
- `algolia_search_index_docs_platform_aware_markdown` - Full-text search with facets
- `algolia_search_for_facet_values` - Browse products/platforms
- `algolia_recommendations` - Find related documentation
## Key Behaviors
**1. Use facets for discovery**
- When users ask about "what's available", explore facets first
- Example: User asks "what video products exist?"
→ Use `algolia_search_for_facet_values(facetName: "product", facetQuery: "video")`
→ Shows video-calling is the main product
**2. Platform-aware searching**
- Include target platform in search queries (iOS, Android, Web, etc.)
- Results are intelligently ranked by platform relevance
**3. Interpret facet results for refinement**
- Search results include facet breakdowns by product and platform
- Use these to suggest filtering: "15 results are for video-calling, 8 for voice-calling"
**4. Maintain session continuity**
- Use same sessionId (UUID) across related searches in a conversation
- Improves ranking for follow-up queries
## Available Products & Platforms
**Key Products:** conversational-ai, video-calling, broadcast-streaming, interactive-live-streaming, agora-chat, voice-calling, signaling, extensions-marketplace, interactive-whiteboard
**Key Platforms:** android, web, ios, windows, macos, unity, flutter, react-native, electron, unreal
## Workflow Example
User: "I'm building an iOS app with Video Calling. Where do I start?"
1. Search: "iOS video calling quickstart"
2. Results show facets indicating video-calling is the best match
3. Suggest: "Most results are for Video Calling, here's the quickstart"
4. Follow-up searches: Keep same sessionId for context
```
#### What This Enables [#what-this-enables]
* Intelligent facet exploration before deep searches
* Platform-aware result refinement suggestions
* Cross-search session continuity for better context
Without the prompt, searches still work but won't leverage faceted browsing patterns.
## AGENTS.md [#agentsmd]
To get the most out of Agora Skills and the MCP server, Agora recommends that you include an [AGENTS.md](https://agents) or similar file in your repository. This file ensures that your agent always consults Agora docs to produce reliable, working code.
Add the following instructions to your own `AGENTS.md` file:
```markdown
## Agora Documentation
Agora documentation is updated frequently. You should always refer to the latest documentation when working with Agora products. Agora offers an MCP server that you can use to browse and search its documentation. If the developer has not yet installed this server, recommend that they install it at https://mcp.agora.io.
If the Agora Skills files are available in your context, use them for platform-specific API guidance and code examples.
```
# Broadcast Streaming overview (/en/realtime-media/broadcast-streaming/product-overview)
Agora's Broadcast Streaming API delivers low-latency, high-definition live video streaming at scale for one-to-many broadcast delivery. With support for cross-platform integration and adaptive streaming technologies, it ensures reliable, high-quality viewing experiences across a wide range of network conditions and devices. Reach global audiences with smooth, uninterrupted broadcasts for large-scale events such as concerts, sports, conferences, live shows, and other live production experiences.
Enhance Agora's Video SDK with broadcast production capabilities such as recording, stream management, content moderation, and audience analytics, or leverage the Extensions Marketplace to enable AI-powered features like noise cancellation, video effects, and more.
## Start building [#start-building]
## Product Features [#product-features]
* **Global coverage** - Agora’s Software-Defined Real-Time Network (SDRTN®) supports video users in over 200 countries and regions.
* **High-quality video at scale** - Consistent high-quality video from few to thousands of concurrent users, even under challenging network conditions.
* **Multiple audio and video tracks** - Publish multiple audio and video tracks to one or more channels from a single instance, with support for multi-channel capture cameras and microphones.
* **Screen sharing and collaboration** - Enable screen sharing or interactive whiteboards that allow users to draw, annotate, and share content from multiple devices simultaneously.
* **AI-powered audio enhancement** - Support for high quality audio with 3D spatial audio, AI noise suppression, and gain control to provide an immersive audio experience.
* **Recording** - Record video sessions in the cloud or on premises with control over the format, path of storage, and quality.
# Quickstart (/en/realtime-media/broadcast-streaming/quickstart)
<_PlatformTabsGroup groupMode="structured" canonicalPlatform="web" platforms="["android","ios","macos","web","windows","electron","flutter","react-native","javascript","unity","unreal","blueprint","python"]" 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 Broadcast Streaming app using the Agora Video SDK.
## Understand the tech [#understand-the-tech]
To start a Broadcast Streaming session, implement the following steps in your app:
* **Initialize the Agora Engine**: Before calling other APIs, create and initialize an Agora Engine instance.
* **Join a channel**: Call methods to create and join a channel.
* **Join as a host**: A live streaming event has one or more hosts. A host publishes audio and video to the channel. Hosts can also subscribe to streams from other hosts.
* **Join as audience**: Audience members can only subscribe to streams published by hosts.
* **Send and receive audio and video**: Hosts publish streams to the channel. Audience members subscribe to audio and video streams published by hosts.

## Prerequisites [#prerequisites]
* [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.
* A camera and a microphone
* A valid Agora account and project. Please refer to [Agora account management](manage-agora-account.md) for details.
## Set up your project [#set-up-your-project]
This section shows you how to set up your Android project and install the Agora Video SDK.
**Create a new project**
1. Create a [new project](https://developer.android.com/studio/projects/create-project).
1. Open Android Studio and select **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.
After you create a project, Android Studio automatically starts gradle sync. Ensure that the synchronization is successful before proceeding to the next step.
**Add to an existing project**
1. Add a new activity to your project.
1. Open your project in Android Studio.
2. Right-click on the `app/src/main/java/` 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.
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.
**Maven Central**
1. Open the `settings.gradle` file in the project's root directory and add the Maven Central dependency, if it doesn't already exist:
```text
repositories {
mavenCentral()
}
```
If your Android project uses dependencyResolutionManagement, the method of adding the Maven Central dependency may differ.
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`
```json
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`.
To get the latest version number, check the [Release notes](reference/release-notes). To integrate the Lite SDK, use `io.agora.rtc:lite-sdk` instead.
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.**
```
**Manual integration**
1. Download the latest version of Video SDK from the [SDKs](/en/api-reference/sdks?product=video\&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.**
```
## Implement Broadcast Streaming [#implement-broadcast-streaming]
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:

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:
Java
Kotlin
```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;
```
```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
```
### 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](manage-agora-account.md), and custom [event handler](#subscribe-to--events), then call `RtcEngine.create(config)` to initialize the engine, enabling further channel operations. In your `MainActivity` file, add the following code:
Java
Kotlin
```java
// Fill in the app ID from Agora Console
private String myAppId = "";
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());
}
}
```
```kotlin
// Fill in the App ID obtained from the Agora Console
private val myAppId = ""
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}")
}
}
```
### 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](build/authenticate-users/deploy-token-server.mdx) in your security infrastructure. For the purpose of this guide [Generate a temporary token](manage-agora-account.md).
* **User ID**: A 32-bit signed integer that identifies a user in the channel. You can specify a unique user ID for each user yourself. If you set the user ID to `0` when joining a channel, the SDK generates a random number for the user ID and returns the value in the `onJoinChannelSuccess` callback.
* **Channel media options**: Configure `ChannelMediaOptions` to define publishing and subscription settings, optimize performance for your specific use-case, and set optional parameters.
For Broadcast Streaming, set the `channelProfile` to `CHANNEL_PROFILE_LIVE_BROADCASTING`, the `clientRoleType` to `CLIENT_ROLE_BROADCASTER` (host) or `CLIENT_ROLE_AUDIENCE`, and the `audienceLatencyLevel` to `AUDIENCE_LATENCY_LEVEL_LOW_LATENCY`.
Java
Kotlin
```java
// Fill in the channel name
private String channelName = "";
// Fill in the temporary token generated from Agora Console
private String token = "";
private void joinChannel() {
// Create an instance of ChannelMediaOptions and configure it
ChannelMediaOptions options = new ChannelMediaOptions();
// Set the user role to BROADCASTER or AUDIENCE according to the use-case
options.clientRoleType = Constants.CLIENT_ROLE_BROADCASTER;
// In the broadcast streaming use-case, set the channelProfile to BROADCASTING
options.channelProfile = Constants.CHANNEL_PROFILE_LIVE_BROADCASTING;
// Set the latency level for audience
options.audienceLatencyLevel = Constants.AUDIENCE_LATENCY_LEVEL_LOW_LATENCY;
// Publish local media
options.publishCameraTrack = true;
options.publishMicrophoneTrack = true;
mRtcEngine.joinChannel(token, channelName, 0, options);
}
```
```kotlin
// Fill in the channel name
private val channelName = ""
// Fill in the temporary token generated from Agora Console
private val token = ""
private fun joinChannel() {
// Create an instance of ChannelMediaOptions and configure it
val options = ChannelMediaOptions().apply {
// Set the user role to BROADCASTER or AUDIENCE according to the use-case
clientRoleType = Constants.CLIENT_ROLE_BROADCASTER
// In the broadcast streaming use-case, set the channelProfile to BROADCASTING
channelProfile = Constants.CHANNEL_PROFILE_LIVE_BROADCASTING
// Set the latency level for audience
audienceLatencyLevel = Constants.AUDIENCE_LATENCY_LEVEL_LOW_LATENCY
// Publish local media
publishMicrophoneTrack = true
publishCameraTrack = true
}
mRtcEngine?.joinChannel(token, channelName, 0, options)
}
```
### 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.
To ensure that you receive all Video SDK events, set the Agora Engine event handler before joining a channel.
Java
Kotlin
```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);
});
}
};
```
```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")
}
}
}
```
### 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.
Java
Kotlin
```java
private void enableVideo() {
mRtcEngine.enableVideo();
mRtcEngine.startPreview();
}
```
```kotlin
private fun enableVideo() {
mRtcEngine?.apply {
enableVideo()
startPreview()
}
}
```
### Display the local video [#display-the-local-video]
Call `setupLocalVideo` to initialize the local view and set the local video display properties.
Java
Kotlin
```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));
}
```
```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))
}
```
### 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.
Java
Kotlin
```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));
}
```
```kotlin
private fun setupRemoteVideo(uid: Int) {
val container = findViewById(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))
}
```
### Handle permissions [#handle-permissions]
To access the camera and microphone on Android devices, declare the necessary permissions in the app's manifest and ensure that the user grants these permissions when the app starts.
1. Open your project's `AndroidManifest.xml` file and add the following permissions before ``:
```xml
```
2. Use the following code to handle runtime permissions in your Android app. The logic ensures that the necessary permissions are granted before starting Broadcast Streaming. In your `MainActivity` file, add the following code:
Java
Kotlin
```java
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) {
if (requestCode == PERMISSION_REQ_ID && checkPermissions()) {
startBroadcastStreaming();
}
}
```
```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 {
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,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == PERMISSION_REQ_ID && checkPermissions()) {
startBroadcastStreaming()
}
}
```
### Start and close the app [#start-and-close-the-app]
When a user launches your app, start real-time interaction. When a user closes the app, stop the interaction.
1. In the `onCreate` callback, check whether the app 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.
Java
Kotlin
```java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (checkPermissions()) {
startBroadcastStreaming();
} else {
requestPermissions();
}
}
```
```kotlin
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (checkPermissions()) {
startBroadcastStreaming()
} else {
requestPermissions()
}
}
```
2. When a user closes the app, or switches the app 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.
Java
Kotlin
```java
private void cleanupAgoraEngine() {
if (mRtcEngine != null) {
mRtcEngine.stopPreview();
mRtcEngine.leaveChannel();
mRtcEngine = null;
}
}
```
```kotlin
private fun cleanupAgoraEngine() {
mRtcEngine?.apply {
stopPreview()
leaveChannel()
}
mRtcEngine = null
}
```
### 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 `` in package `com.example.` with your project's name.
Java
Kotlin
```java
package com.example.
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;
// Fill in the app ID from Agora Console
private String myAppId = "";
// Fill in the channel name
private String channelName = "";
// Fill in the temporary token generated from Agora Console
private String 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()) {
startBroadcastStreaming();
} 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()) {
startBroadcastStreaming();
}
}
private void startBroadcastStreaming() {
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() {
// Create an instance of ChannelMediaOptions and configure it
ChannelMediaOptions options = new ChannelMediaOptions();
// Set the user role to BROADCASTER or AUDIENCE according to the use-case
options.clientRoleType = Constants.CLIENT_ROLE_BROADCASTER;
// In the live broadcast use-case, set the channel profile to BROADCASTING (live broadcast use-case)
options.channelProfile = Constants.CHANNEL_PROFILE_LIVE_BROADCASTING;
// Set the audience latency level
options.audienceLatencyLevel = Constants.AUDIENCE_LATENCY_LEVEL_LOW_LATENCY;
// Publish local media
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());
}
}
```
```kotlin
package com.example.
import android.Manifest
import android.content.pm.PackageManager
import android.os.Build
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() {
private val PERMISSION_REQ_ID = 22
private val myAppId = ""
private val channelName = ""
private val 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)
runOnUiThread {
showToast("Joined channel $channel")
}
}
override fun onUserJoined(uid: Int, elapsed: Int) {
runOnUiThread {
setupRemoteVideo(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()) {
startBroadcastStreaming()
} else {
requestPermissions()
}
}
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 {
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,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == PERMISSION_REQ_ID && checkPermissions()) {
startBroadcastStreaming()
}
}
private fun startBroadcastStreaming() {
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 = findViewById(R.id.local_video_view_container)
val surfaceView = SurfaceView(this)
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_LIVE_BROADCASTING
audienceLatencyLevel = Constants.AUDIENCE_LATENCY_LEVEL_LOW_LATENCY
publishMicrophoneTrack = true
publishCameraTrack = true
}
mRtcEngine?.joinChannel(token, channelName, 0, options)
}
private fun setupRemoteVideo(uid: Int) {
val container = findViewById(R.id.remote_video_view_container)
val surfaceView = SurfaceView(this).apply {
setZOrderMediaOverlay(true)
}
container.addView(surfaceView)
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, message, Toast.LENGTH_SHORT).show()
}
}
}
```
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.
### 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.

**Sample code to create the user interface**
```xml
```
## 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  **Sync Project with Gradle Files** to resolve project dependencies and update the configuration.
4. After synchronization is successful, click  **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 app. 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 to this product.
* If a firewall is deployed in your network environment, refer to [Connect with Cloud Proxy](build/optimize-quality-and-connection/cloud-proxy.mdx) to use Agora services normally.
### Next steps [#next-steps]
After implementing the quickstart sample, read the following documents to learn more:
* To ensure communication security in a test or production environment, best practice is to obtain and use a token from an authentication server. For details, see [Secure authentication with tokens](build/authenticate-users/use-tokens.mdx).
### Sample project [#sample-project]
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)
* [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]
* [Error codes](reference/error-codes.md)
* [Connection status management](build/optimize-quality-and-connection/connection-status-management.mdx)
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="ios">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="ios" />
This page provides a step-by-step guide on how to create a basic Broadcast Streaming app using the Agora Video SDK.
## Understand the tech [#understand-the-tech-1]
To start a Broadcast Streaming session, implement the following steps in your app:
* **Initialize the Agora Engine**: Before calling other APIs, create and initialize an Agora Engine instance.
* **Join a channel**: Call methods to create and join a channel.
* **Join as a host**: A live streaming event has one or more hosts. A host publishes audio and video to the channel. Hosts can also subscribe to streams from other hosts.
* **Join as audience**: Audience members can only subscribe to streams published by hosts.
* **Send and receive audio and video**: Hosts publish streams to the channel. Audience members subscribe to audio and video streams published by hosts.

## Prerequisites [#prerequisites-1]
* Xcode 13.0 or higher.
* An Apple developer account.
* If you need to use CocoaPods integrated SDK, make sure [CocoaPods is installed](https://guides.cocoapods.org/using/getting-started.html#getting-started).
* Two devices running iOS 14.0 or higher.
* A camera and a microphone
* A valid Agora account and project. Please refer to [Agora account management](manage-agora-account.md) for details.
## Set up your project [#set-up-your-project-1]
This section shows you how to set up your iOS project and install the Agora Video SDK.
**Create a new project**
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.
If you have not added the development team information, you see the **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.
2. [Set up automatic signing](https://help.apple.com/xcode/mac/current/#/dev23aab79b4) for your projects.
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.
**Add to an existing project**
Follow these steps to add Broadcast Streaming 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.
### Install the SDK [#install-the-sdk-1]
Use one of the following methods to install Video SDK.
**Swift Package Manager**
1. In Xcode, go to **File** > **Add Package Dependencies**.
2. In the search bar, past the following URL:
```json
{`https://github.com/AgoraIO/AgoraRtcEngine_iOS.git`}
```
3. Click **Add Package**, select the latest version, and click **Next**.
4. For basic Broadcast Streaming, 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 further information, refer to [Apple's official documentation](https://help.apple.com/xcode/mac/current/#/devb83d64851).
**CocoaPods**
1. Go to the project root directory in the terminal and run the `pod init` command. 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
# For x.y.z fill in 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
```
Obtain the latest version number from the [release notes](reference/release-notes).
3. Run the `pod install` command in the terminal to install the Video SDK. After successful installation, the terminal shows **Pod installation complete!**.
4. After successful installation, a file with the suffix `.xcworkspace` is generated in the project folder. Open the file through Xcode for subsequent operations.
**Manual integration**
1. Download the latest version of the SDK from [SDKs download](/en/api-reference/sdks?product=video\&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**.
Agora SDK uses `libc++` (LLVM) by default. If you need to use `libstdc++` (GNU), please contact [support@agora.io](mailto\:support@agora.io). The library provided by the SDK is a FAT Image, which includes 32/64-bit simulator and 32/64-bit real machine versions.
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.
## Implement Broadcast Streaming [#implement-broadcast-streaming-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:

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 AgoraRtcKit
```
### Initialize the engine [#initialize-the-engine-1]
Call `sharedEngine(withAppId:delegate:)` to create and initialize an `AgoraRtcEngineKit` instance. Provide your [App ID](manage-agora-account.md) and an `AgoraRtcEngineDelegate` implementation to [handle SDK events](#subscribe-to--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](build/authenticate-users/deploy-token-server.mdx) in your security infrastructure. For the purpose of this guide [Generate a temporary token](manage-agora-account.md).
* **User ID**: A 32-bit signed integer that identifies a user in the channel. You can specify a unique user ID for each user yourself. If you set the user ID to `0` when joining a channel, the SDK generates a random number for the user ID and returns the value in the `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 Broadcast Streaming, set the `channelProfile` to `.liveBroadcasting`, the `clientRoleType` to `.broadcaster` (host) or `.audience`, and the `audienceLatencyLevel` to `lowLatency`.
```swift
let channelName = "demo" // Replace with your actual channel name
let token = "" // Replace with your token
// Join the channel with specified options
func joinChannel() {
let options = AgoraRtcChannelMediaOptions()
// In a live streaming use-case, set the channel use-case to liveBroadcasting
options.channelProfile = .liveBroadcasting
// 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
// Set the audience latency level
options.audienceLatencyLevel = .lowLatency
// Use a temporary Token to join the channel
agoraKit.joinChannel(
byToken: token,
channelId: channelName,
uid: 0,
mediaOptions: options
)
}
```
You can also set the latency level after joining a channel by calling the `setClientRole` method with `role` set to `AgoraClientRole.audience` and `options.audienceLatencyLevel` set to `lowLatency`.
```swift
// Set the user role to audience
let role = AgoraClientRole.audience
let options = AgoraClientRoleOptions()
// Set the low latency level
options.audienceLatencyLevel = .lowLatency
agoraKit.setClientRole(role, options: 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:
To ensure that you receive all Video SDK events, set the Agora Engine event handler before joining a channel.
```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 iOS 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. |
* 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).
### Start and close the app [#start-and-close-the-app-1]
When the user launches the app, it joins the channel and starts Broadcast Streaming. When the user closes the app, it leaves the channel and ends Broadcast Streaming.
1. To start Broadcast Streaming, 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()
```
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.
### 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:
```swift
// ViewController.swift
import UIKit
import AgoraRtcKit
class ViewController: UIViewController {
let appId = "" // Replace with your actual App ID
let channelName = "demo" // Replace with your actual channel name
let 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 a live streaming use-case, set the channel use-case to liveBroadcasting
options.channelProfile = .liveBroadcasting
// 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
// Set the audience latency level
options.audienceLatencyLevel = .lowLatency
// 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) {
// 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)
}
}
```
### 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**
```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 iOS device, repeat the previous steps to install and launch the app. 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 to this product.
* If a firewall is deployed in your network environment, refer to [Connect with Cloud Proxy](build/optimize-quality-and-connection/cloud-proxy.mdx) to use Agora services normally.
### Next steps [#next-steps-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](build/authenticate-users/use-tokens.mdx).
### 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 iOS 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:
2. Choose **File > New File**.
3. Scroll down to the **Resource** section and select **App Privacy File** type.
4. Click **Next**.
5. Check your app in the **Targets** list.
6. Click **Create**.
The default file name is `PrivacyInfo.xcprivacy` which is also the required file name for bundled privacy manifests.
7. Add the items in `PrivacyInfo.xcprivacy` file of the Video SDK to `PrivacyInfo.xcprivacy` of the app using the following source code:
```xml
NSPrivacyTrackingNSPrivacyCollectedDataTypesNSPrivacyAccessedAPITypesNSPrivacyAccessedAPITypeNSPrivacyAccessedAPICategorySystemBootTimeNSPrivacyAccessedAPITypeReasons35F9.1NSPrivacyAccessedAPITypeNSPrivacyAccessedAPICategoryFileTimestampNSPrivacyAccessedAPITypeReasonsDDA9.1NSPrivacyAccessedAPITypeNSPrivacyAccessedAPICategoryDiskSpaceNSPrivacyAccessedAPITypeReasonsE174.1
```
### 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\:info\:uid\: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)
* [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)
* [What can I do if I get a pop-up warning saying 'the framework cannot be opened' when compiling an Xcode project?](/en/api-reference/faq/integration/framework_cannot_be_opened)
* [How can I add a privacy manifest to my iOS app?](/en/api-reference/faq/other/ios_privacy_manifest)
### See also [#see-also-1]
* [Error codes](reference/error-codes.md)
* [Connection status management](build/optimize-quality-and-connection/connection-status-management.mdx)
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="macos">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="macos" />
This page provides a step-by-step guide on how to create a basic Broadcast Streaming app using the Agora Video SDK.
## Understand the tech [#understand-the-tech-2]
To start a Broadcast Streaming session, implement the following steps in your app:
* **Initialize the Agora Engine**: Before calling other APIs, create and initialize an Agora Engine instance.
* **Join a channel**: Call methods to create and join a channel.
* **Join as a host**: A live streaming event has one or more hosts. A host publishes audio and video to the channel. Hosts can also subscribe to streams from other hosts.
* **Join as audience**: Audience members can only subscribe to streams published by hosts.
* **Send and receive audio and video**: Hosts publish streams to the channel. Audience members subscribe to audio and video streams published by hosts.

## Prerequisites [#prerequisites-2]
* Xcode 13.0 or higher.
* An Apple developer account.
* If you need to use CocoaPods integrated SDK, make sure [CocoaPods is installed](https://guides.cocoapods.org/using/getting-started.html#getting-started).
* Two devices running macOS 10.10 or higher.
* A camera and a microphone
* A valid Agora account and project. Please refer to [Agora account management](manage-agora-account.md) for details.
## Set up your project [#set-up-your-project-2]
This section shows you how to set up your macOS project and install the Agora Video SDK.
**Create a new project**
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.
If you have not added the development team information, you see the **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.
2. [Set up automatic signing](https://help.apple.com/xcode/mac/current/#/dev23aab79b4) for your projects.
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.
**Add to an existing project**
Follow these steps to add Broadcast Streaming 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.
### Install the SDK [#install-the-sdk-2]
Use one of the following methods to install Video SDK.
**Swift Package Manager**
1. In Xcode, go to **File** > **Add Package Dependencies**.
2. In the search bar, past the following URL:
```json
{`https://github.com/AgoraIO/AgoraRtcEngine_macOS.git`}
```
3. Click **Add Package**, select the latest version, and click **Next**.
4. For basic Broadcast Streaming, 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 further information, refer to [Apple's official documentation](https://help.apple.com/xcode/mac/current/#/devb83d64851).
**CocoaPods**
1. Go to the project root directory in the terminal and run the `pod init` command. 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
# For x.y.z fill in the specific SDK version number, such as 4.4.0.
pod 'AgoraRtcEngine_macOS', 'x.y.z'
end
```
Obtain the latest version number from the [release notes](reference/release-notes).
3. Run the `pod install` command in the terminal to install the Video SDK. After successful installation, the terminal shows **Pod installation complete!**.
4. After successful installation, a file with the suffix `.xcworkspace` is generated in the project folder. Open the file through Xcode for subsequent operations.
**Manual integration**
1. Download the latest version of the SDK from [SDKs download](/en/api-reference/sdks?product=video\&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**.
Agora SDK uses `libc++` (LLVM) by default. If you need to use `libstdc++` (GNU), please contact [support@agora.io](mailto\:support@agora.io). The library provided by the SDK is a FAT Image, which includes 32/64-bit simulator and 32/64-bit real machine versions.
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.
## Implement Broadcast Streaming [#implement-broadcast-streaming-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:

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 AgoraRtcKit
```
### Initialize the engine [#initialize-the-engine-2]
Call `sharedEngine(withAppId:delegate:)` to create and initialize an `AgoraRtcEngineKit` instance. Provide your [App ID](manage-agora-account.md) and an `AgoraRtcEngineDelegate` implementation to [handle SDK events](#subscribe-to--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](build/authenticate-users/deploy-token-server.mdx) in your security infrastructure. For the purpose of this guide [Generate a temporary token](manage-agora-account.md).
* **User ID**: A 32-bit signed integer that identifies a user in the channel. You can specify a unique user ID for each user yourself. If you set the user ID to `0` when joining a channel, the SDK generates a random number for the user ID and returns the value in the `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 Broadcast Streaming, set the `channelProfile` to `.liveBroadcasting`, the `clientRoleType` to `.broadcaster` (host) or `.audience`, and the `audienceLatencyLevel` to `lowLatency`.
```swift
let channelName = "demo" // Replace with your actual channel name
let token = "" // Replace with your token
// Join the channel with specified options
func joinChannel() {
let options = AgoraRtcChannelMediaOptions()
// In a live streaming use-case, set the channel use-case to liveBroadcasting
options.channelProfile = .liveBroadcasting
// 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
// Set the audience latency level
options.audienceLatencyLevel = .lowLatency
// Use a temporary Token to join the channel
agoraKit.joinChannel(
byToken: token,
channelId: channelName,
uid: 0,
mediaOptions: options
)
}
```
You can also set the latency level after joining a channel by calling the `setClientRole` method with `role` set to `AgoraClientRole.audience` and `options.audienceLatencyLevel` set to `lowLatency`.
```swift
// Set the user role to audience
let role = AgoraClientRole.audience
let options = AgoraClientRoleOptions()
// Set the low latency level
options.audienceLatencyLevel = .lowLatency
agoraKit.setClientRole(role, options: 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:
To ensure that you receive all Video SDK events, set the Agora Engine event handler before joining a channel.
```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 macOS 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. |
* 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).
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 Broadcast Streaming. When the user closes the app, it leaves the channel and ends Broadcast Streaming.
1. To start Broadcast Streaming, 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()
```
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.
### 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:
```swift
// ViewController.swift
import Cocoa
import AgoraRtcKit
class ViewController: NSViewController {
let appId = "" // Replace with your actual App ID
let channelName = "demo" // Replace with your actual channel name
let 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 a live streaming use-case, set the channel use-case to liveBroadcasting
options.channelProfile = .liveBroadcasting
// 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
// Set the audience latency level
options.audienceLatencyLevel = .lowLatency
// Use a temporary Token to join the channel
// If you set uid=0, the engine generates a uid internally; on success, it triggers didJoinChannel callback
agoraKit.joinChannel(byToken: "<#Your Token#>", channelId: "<#Your Channel Name#>", 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)
}
}
```
### 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**
```swift
// ViewController.swift
import Cocoa
import AgoraRtcKit
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 macOS device, repeat the previous steps to install and launch the app. 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 to this product.
* If a firewall is deployed in your network environment, refer to [Connect with Cloud Proxy](build/optimize-quality-and-connection/cloud-proxy.mdx) to use Agora services normally.
### Next steps [#next-steps-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](build/authenticate-users/use-tokens.mdx).
### 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\:info\:uid\: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)
### See also [#see-also-2]
* [Error codes](reference/error-codes.md)
* [Connection status management](build/optimize-quality-and-connection/connection-status-management.mdx)
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="web">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="web" />
Explore sample implementations to quickly integrate Conversational AI.
* [RTC SDK API examples](https://github.com/AgoraIO/API-Examples-Web): Sample projects for the Agora RTC Web SDK 4.x, covering basic, advanced examples for Vue and React.
This page provides a step-by-step guide on how to create a basic Broadcast Streaming app using the Agora Video SDK.
## Understand the tech [#understand-the-tech-3]
To start a Broadcast Streaming session, implement the following steps in your app:
* **Initialize the Agora Engine**: Before calling other APIs, create and initialize an Agora Engine instance.
* **Join a channel**: Call methods to create and join a channel.
* **Join as a host**: A live streaming event has one or more hosts. A host publishes audio and video to the channel. Hosts can also subscribe to streams from other hosts.
* **Join as audience**: Audience members can only subscribe to streams published by hosts.
* **Send and receive audio and video**: Hosts publish streams to the channel. Audience members subscribe to audio and video streams published by hosts.

## Prerequisites [#prerequisites-3]
* A [supported browser](reference/supported-platforms.md).
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)
* A camera and a microphone
* A valid Agora account and project. Please refer to [Agora account management](manage-agora-account.md) for details.
## Set up your project [#set-up-your-project-3]
This section shows you how to set up your Web project and install the Agora Video SDK.
**Create a new project**
To initialize a new Vite project, take the following steps:
1. Open a terminal and run the following command:
```bash
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:
```bash
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.
**Add to an existing project**
To add Broadcast Streaming 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
```
### Install the SDK [#install-the-sdk-3]
Add the Video SDK to your project:
```bash
npm install agora-rtc-sdk-ng
```
## Implement Broadcast Streaming [#implement-broadcast-streaming-3]
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 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 Broadcast Streaming Set `mode` to `live`.
```javascript
// RTC client instance
let client = null;
// Initialize the AgoraRTC client
function initializeClient() {
client = AgoraRTC.createClient({ mode: "live", codec: "vp8", role: "host" });
setupEventListeners();
}
```
### Join a channel [#join-a-channel-3]
To join a channel, call `join` with the following parameters:
* **App ID**: The [App ID](manage-agora-account.md) 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](build/authenticate-users/deploy-token-server.mdx) in your security infrastructure. For the purpose of this guide [Generate a temporary token](manage-agora-account.md).
* **User ID**: A 32-bit signed integer that identifies a user in the channel. You can specify a unique user ID for each user yourself. If you set the user ID to `0` when joining a channel, the SDK generates a random number for the user ID.
When you join as a host, you can publish local media streams in the channel; you can also subscribe to streams published by other hosts. As an audience member, you may only subscribe to media streams published by hosts.
* Join as a host
```js
// Join as a host
async function joinChannel() {
await client.join(appId, channel, token, uid);
// A host can both publish tracks and subscribe to tracks
client.setClientRole("host", clientRoleOptions);
// Create and publish local tracks
await createLocalMediaTracks();
await publishLocalTracks();
displayLocalVideo();
}
```
* Join as audience
To join a channel as an audience member, first call `join`, then use `setClientRole` to set the role. Listen for the `user-published` callback to play the tracks published in the channel.
```js
// Join as audience
async function joinAsAudience() {
await client.join(appId, channel, token, uid);
// Low audience latency level for broadcast streaming
let clientRoleOptions = { level: 1 };
// Audience can only subscribe to tracks
client.setClientRole("audience", clientRoleOptions);
}
```
### 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--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("");
}
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`.
To ensure that you receive all Video SDK events, set up event listeners before joining a channel.
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.
```js
import AgoraRTC from "agora-rtc-sdk-ng";
// RTC client instance
let client = null;
// Declare variables for 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: "live", codec: "vp8", role: "host" });
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", async (user) => {
const remotePlayerContainer = document.getElementById(user.uid);
remotePlayerContainer && remotePlayerContainer.remove();
});
}
// Create and publish local tracks
async function createLocalTracks() {
localAudioTrack = await AgoraRTC.createMicrophoneAudioTrack();
localVideoTrack = await AgoraRTC.createCameraVideoTrack();
}
// 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);
}
// Join as a host
async function joinAsHost() {
await client.join(appId, channel, token, uid);
// A host can both publish tracks and subscribe to tracks
client.setClientRole("host");
// Create and publish local tracks
await createLocalTracks();
await publishLocalTracks();
displayLocalVideo();
disableJoinButtons();
console.log("Host joined and published tracks.");
}
// Join as audience
async function joinAsAudience() {
await client.join(appId, channel, token, uid);
// Set low latency level
let clientRoleOptions = { level: 1 };
// Audience can only subscribe to tracks
client.setClientRole("audience", clientRoleOptions);
disableJoinButtons();
console.log("Audience joined.");
}
// Publish local tracks
async function publishLocalTracks() {
await client.publish([localAudioTrack, localVideoTrack]);
}
// 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
async function leaveChannel() {
if (localAudioTrack) {
localAudioTrack.close();
localAudioTrack = null;
}
if (localVideoTrack) {
localVideoTrack.close();
localVideoTrack = null;
}
const localPlayerContainer = document.getElementById(uid);
localPlayerContainer && localPlayerContainer.remove();
client.remoteUsers.forEach((user) => {
const playerContainer = document.getElementById(user.uid);
playerContainer && playerContainer.remove();
});
await client.leave();
enableJoinButtons();
console.log("Left the channel.");
}
// Disable join buttons
function disableJoinButtons() {
document.getElementById("host-join").disabled = true;
document.getElementById("audience-join").disabled = true;
}
// Enable join buttons
function enableJoinButtons() {
document.getElementById("host-join").disabled = false;
document.getElementById("audience-join").disabled = false;
}
// Set up event listeners for buttons
function setupButtonHandlers() {
document.getElementById("host-join").onclick = joinAsHost;
document.getElementById("audience-join").onclick = joinAsAudience;
document.getElementById("leave").onclick = leaveChannel;
}
// Start live streaming
function startBasicLiveStreaming() {
initializeClient();
window.onload = setupButtonHandlers;
}
startBasicLiveStreaming();
```
### 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**
```html
Agora Web SDK Quickstart
Agora Web SDK Quickstart
```
## 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:
```bash
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:

4. On a second device, repeat the previous steps to install and launch the app. 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:
* 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; the audience can see the host in the remote video view and hear the host.
* 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`).
## Reference [#reference-3]
This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.
* If a firewall is deployed in your network environment, refer to [Connect with Cloud Proxy](build/optimize-quality-and-connection/cloud-proxy.mdx) to use Agora services normally.
### Next steps [#next-steps-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](build/authenticate-users/use-tokens.mdx).
### 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+) |
* 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.
### 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:

### 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](build/optimize-quality-and-connection/app-size-optimization.mdx).
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
```
* 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
```
Visit the [Download](/en/api-reference/sdks?product=video\&platform=web) page to obtain the link for the latest SDK version.
### Frequently asked questions [#frequently-asked-questions-2]
**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):
```bash
export NODE_OPTIONS=--openssl-legacy-provider
```
* Temporarily switch to a lower version of Node.js.
### See also [#see-also-3]
* [Error codes](reference/error-codes.md)
* [Connection status management](build/optimize-quality-and-connection/connection-status-management.mdx)
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="windows">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="windows" />
This page provides a step-by-step guide on how to create a basic Broadcast Streaming app using the Agora Video SDK.
## Understand the tech [#understand-the-tech-4]
To start a Broadcast Streaming session, implement the following steps in your app:
* **Initialize the Agora Engine**: Before calling other APIs, create and initialize an Agora Engine instance.
* **Join a channel**: Call methods to create and join a channel.
* **Join as a host**: A live streaming event has one or more hosts. A host publishes audio and video to the channel. Hosts can also subscribe to streams from other hosts.
* **Join as audience**: Audience members can only subscribe to streams published by hosts.
* **Send and receive audio and video**: Hosts publish streams to the channel. Audience members subscribe to audio and video streams published by hosts.

## Prerequisites [#prerequisites-4]
* 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. 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).
* A camera and a microphone
* A valid Agora account and project. Please refer to [Agora account management](manage-agora-account.md) for details.
## Set up your project [#set-up-your-project-4]
This section shows you how to set up your Windows project and install the Agora Video SDK.
**Create a new project**
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**.
**Add to an existing project**
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.
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?product=video\&platform=windows).
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 Broadcast Streaming [#implement-broadcast-streaming-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:

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](manage-agora-account.md), and custom [event handler](#subscribe-to--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](build/authenticate-users/deploy-token-server.mdx) in your security infrastructure. For the purpose of this guide [Generate a temporary token](manage-agora-account.md).
* **User ID**: A 32-bit signed integer that identifies a user in the channel. You can specify a unique user ID for each user yourself. If you set the user ID to `0` when joining a channel, the SDK generates a random number for the user ID and returns the value in the `onJoinChannelSuccess` callback.
* **Channel media options**: Configure `ChannelMediaOptions` to define publishing and subscription settings, optimize performance for your specific use-case, and set optional parameters.
For Broadcast Streaming, set the `channelProfile` to `CHANNEL_PROFILE_LIVE_BROADCASTING`, the `clientRoleType` to `CLIENT_ROLE_BROADCASTER` (host) or `CLIENT_ROLE_AUDIENCE`, and the `audienceLatencyLevel` to `AUDIENCE_LATENCY_LEVEL_LOW_LATENCY`.
```cpp
void CAgoraQuickStartDlg::joinChannel(const char* token, const char* channelName) {
ChannelMediaOptions options;
// Set channel profile to live broadcasting
options.channelProfile = CHANNEL_PROFILE_LIVE_BROADCASTING;
// Set user role to broadcaster; keep default value if setting user role to audience
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;
// Specify the audio latency level
options.audienceLatencyLevel = AUDIENCE_LATENCY_LEVEL_LOW_LATENCY;
// Join the channel using the token and channel name (both are const char*)
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.
To ensure that you receive all Video SDK events, set the Agora Engine event handler before joining a channel.
```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:
2. Set the video rendering mode.
3. Specify the user ID (`uid`).
4. Define the display window.
5. Call the `setupLocalVideo` method to apply the `VideoCanvas` configuration.
6. 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 app 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;
```
After you call `Dispose`, you can no longer use any SDK methods or callbacks. To use real-time Broadcast Streaming again, you must create a new engine. For more information, see [Initialize the Engine](#initialize-the-engine).
### 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**
```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**
```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() {
if (m_rtcEngine) {
m_rtcEngine->release(true);
m_rtcEngine = NULL;
}
}
void CAgoraQuickStartDlg::DoDataExchange(CDataExchange* pDX) {
CDialog::DoDataExchange(pDX);
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)
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()
// Insert your project's App ID obtained from the Agora Console
#define APP_ID ""
// Insert the temporary token obtained from the Agora Console
#define token ""
void CAgoraQuickStartDlg::initializeAgoraEngine() {
m_rtcEngine = createAgoraRtcEngine();
RtcEngineContext context;
context.appId = APP_ID;
context.eventHandler = &m_eventHandler;
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"));
}
}
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);
}
}
void CAgoraQuickStartDlg::OnPaint() {
if (IsIconic()) {
CPaintDC dc(this);
SendMessage(WM_ICONERASEBKGND, reinterpret_cast(dc.GetSafeHdc()), 0);
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;
dc.DrawIcon(x, y, m_hIcon);
} else {
CDialog::OnPaint();
}
}
HCURSOR CAgoraQuickStartDlg::OnQueryDragIcon() {
return static_cast(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;
options.channelProfile = CHANNEL_PROFILE_LIVE_BROADCASTING;
options.clientRoleType = CLIENT_ROLE_BROADCASTER;
options.publishMicrophoneTrack = true;
options.publishCameraTrack = true;
options.autoSubscribeAudio = true;
options.autoSubscribeVideo = true;
options.audienceLatencyLevel = AUDIENCE_LATENCY_LEVEL_LOW_LATENCY;
m_rtcEngine->joinChannel(token, channelName, 0, options);
}
void CAgoraQuickStartDlg::setupRemoteVideo() {
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() {
VideoCanvas canvas;
canvas.renderMode = RENDER_MODE_TYPE::RENDER_MODE_HIDDEN;
canvas.uid = 0;
canvas.view = m_staLocal.GetSafeHwnd();
m_rtcEngine->setupLocalVideo(canvas);
m_rtcEngine->startPreview();
}
void CAgoraQuickStartDlg::OnBnClickedBtnJoin() {
CString strChannelName;
m_edtChannelName.GetWindowText(strChannelName);
if (strChannelName.IsEmpty()) {
AfxMessageBox(_T("Fill channel name first"));
return;
}
joinChannel(token, CW2A(strChannelName));
setupLocalVideo();
}
void CAgoraQuickStartDlg::OnBnClickedBtnLeave() {
m_rtcEngine->leaveChannel();
VideoCanvas canvas;
canvas.uid = 0;
m_rtcEngine->setupLocalVideo(canvas);
m_rtcEngine->startPreview();
m_remoteRender = false;
}
LRESULT CAgoraQuickStartDlg::OnEIDJoinChannelSuccess(WPARAM wParam, LPARAM lParam) {
return 0;
}
LRESULT CAgoraQuickStartDlg::OnEIDUserJoined(WPARAM wParam, LPARAM lParam) {
uid_t remoteUid = wParam;
if (m_remoteRender) {
return 0;
}
setupRemoteVideo(remoteUid);
return 0;
}
LRESULT CAgoraQuickStartDlg::OnEIDUserOffline(WPARAM wParam, LPARAM lParam) {
uid_t remoteUid = wParam;
if (!m_remoteRender) {
return 0;
}
VideoCanvas canvas;
canvas.uid = remoteUid;
m_rtcEngine->setupRemoteVideo(canvas);
m_remoteRender = false;
return 0;
}
```
### 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**
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:

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 to this product.
* If a firewall is deployed in your network environment, refer to [Connect with Cloud Proxy](build/optimize-quality-and-connection/cloud-proxy.mdx) to use Agora services normally.
### Next steps [#next-steps-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](build/authenticate-users/use-tokens.mdx).
### 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-3]
* [How can I fix black screen issues?](/en/api-reference/faq/quality/video_blank)
* [Why can't I turn on the camera?](/en/api-reference/faq/quality/video_camera)
* [How can I listen for audience joining or leaving a channel?](/en/api-reference/faq/integration/audience_event)
* [How can I solve channel-related issues?](/en/api-reference/faq/integration/channel)
* [How can I set the log file?](/en/api-reference/faq/integration/set_log_file)
### See also [#see-also-4]
* [Error codes](reference/error-codes.md)
* [Connection status management](build/optimize-quality-and-connection/connection-status-management.mdx)
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="electron">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="electron" />
This page provides a step-by-step guide on how to create a basic Broadcast Streaming app using the Agora Video SDK.
## Understand the tech [#understand-the-tech-5]
To start a Broadcast Streaming session, implement the following steps in your app:
* **Initialize the Agora Engine**: Before calling other APIs, create and initialize an Agora Engine instance.
* **Join a channel**: Call methods to create and join a channel.
* **Join as a host**: A live streaming event has one or more hosts. A host publishes audio and video to the channel. Hosts can also subscribe to streams from other hosts.
* **Join as audience**: Audience members can only subscribe to streams published by hosts.
* **Send and receive audio and video**: Hosts publish streams to the channel. Audience members subscribe to audio and video streams published by hosts.

## Prerequisites [#prerequisites-5]
* [Node.js](https://nodejs.org/en/download/) 14 or higher.
* A camera and a microphone
* A valid Agora account and project. Please refer to [Agora account management](manage-agora-account.md) for details.
## Set up your project [#set-up-your-project-5]
This section shows you how to set up your Electron project and install the Agora Video SDK.
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]
**npm integration**
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.
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.
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
```
* 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.
**Manual Integration**
Compile the latest SDK from the [Electron SDK repository](https://github.com/AgoraIO-Extensions/Electron-SDK/tree/main/) and integrate it into your application.
## Implement Broadcast Streaming [#implement-broadcast-streaming-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:

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,
AudienceLatencyLevelType
} = 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](manage-agora-account.md). Call `registerEventHandler` to register a custom [event handler](#subscribe-to--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](build/authenticate-users/deploy-token-server.mdx) in your security infrastructure. For the purpose of this guide [Generate a temporary token](manage-agora-account.md).
* **User ID**: A 32-bit signed integer that identifies a user in the channel. You can specify a unique user ID for each user yourself. If you set the user ID to `0` when joining a channel, the SDK generates a random number for the user ID and returns the value in the `onJoinChannelSuccess` callback.
* **Channel media options**: Configure `ChannelMediaOptions` to define publishing and subscription settings, optimize performance for your specific use-case, and set optional parameters.
For Broadcast Streaming, set the `channelProfile` to `ChannelProfileLiveBroadcasting`, the `clientRoleType` to `ClientRoleBroadcaster` (host) or `ClientRoleAudience`, and the `audienceLatencyLevelType` to `AudienceLatencyLevelLowLatency`.
```js
// Join the channel using a temporary token
rtcEngine.joinChannel(token, channel, uid, {
// Set the channel profile to live broadcasting
channelProfile: ChannelProfileType.ChannelProfileLiveBroadcasting,
// 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,
// For live streaming use-case (This setting takes effect only when the user role is set to ClientRoleAudience )
audienceLatencyLevelType: AudienceLatencyLevelType.AudienceLatencyLevelLowLatency,
});
```
### 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);
```
To ensure that you receive all Video SDK events, set the Agora Engine event handler before joining a channel.
### 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 app receives the `onUserJoined` callback.
### Start and close the app [#start-and-close-the-app-3]
When a user launches your app, start Broadcast Streaming. When a user closes the app, stop Broadcast Streaming and release resources.
1. To start Broadcast Streaming, [initialize the engine](#initialize-the-engine), [display the local video](#display-the-local-video) and [join a channel](#join-a-channel).
2. When Broadcast Streaming 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();
```
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.
### 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`**
```js
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`**
```js
const {
createAgoraRtcEngine,
ChannelProfileType,
ClientRoleType,
VideoSourceType,
VideoViewSetupMode,
AudienceLatencyLevelType
} = 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 live broadcasting
channelProfile: ChannelProfileType.ChannelProfileLiveBroadcasting,
// 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,
// For live streaming use-case (This setting takes effect only when the user role is set to ClientRoleAudience )
audienceLatencyLevelType: AudienceLatencyLevelType.AudienceLatencyLevelLowLatency,
});
};
```
* 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](#get-device-permissions).
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**
```html
Electron Quickstart
Electron Quickstart
```
## 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 to this product.
* If a firewall is deployed in your network environment, refer to [Connect with Cloud Proxy](build/optimize-quality-and-connection/cloud-proxy.mdx) to use Agora services normally.
### Next steps [#next-steps-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](build/authenticate-users/use-tokens.mdx).
### 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-4]
* [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)
* [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]
* [Error codes](reference/error-codes.md)
* [Connection status management](build/optimize-quality-and-connection/connection-status-management.mdx)
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="flutter">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="flutter" />
This page provides a step-by-step guide on how to create a basic Broadcast Streaming app using the Agora Video SDK.
## Understand the tech [#understand-the-tech-6]
To start a Broadcast Streaming session, implement the following steps in your app:
* **Initialize the Agora Engine**: Before calling other APIs, create and initialize an Agora Engine instance.
* **Join a channel**: Call methods to create and join a channel.
* **Join as a host**: A live streaming event has one or more hosts. A host publishes audio and video to the channel. Hosts can also subscribe to streams from other hosts.
* **Join as audience**: Audience members can only subscribe to streams published by hosts.
* **Send and receive audio and video**: Hosts publish streams to the channel. Audience members subscribe to audio and video streams published by hosts.

## Prerequisites [#prerequisites-6]
* [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 any other 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](reference/supported-platforms.md).
Run the `flutter doctor` command to confirm that your development environment is set up correctly for Flutter development.
* A camera and a microphone
* A valid Agora account and project. Please refer to [Agora account management](manage-agora-account.md) for details.
## Set up your project [#set-up-your-project-6]
This section shows you how to set up your Flutter project and install the Agora Video SDK.
**Create a new project**
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
```
**Add to an existing project**
To add Broadcast Streaming 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`.
### 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 Broadcast Streaming [#implement-broadcast-streaming-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:

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](manage-agora-account.md), and other configuration parameters. In your dart file, add the following code:
```dart
// Set up the Agora RTC engine instance
Future _initializeAgoraVideoSDK() async {
_engine = createAgoraRtcEngine();
await _engine.initialize(const RtcEngineContext(
appId: appId,
channelProfile: ChannelProfileType.channelProfileLiveBroadcasting,
));
}
```
### 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](build/authenticate-users/deploy-token-server.mdx) in your security infrastructure. For the purpose of this guide [Generate a temporary token](manage-agora-account.md).
* **User ID**: A 32-bit signed integer that identifies a user in the channel. You can specify a unique user ID for each user yourself. If you set the user ID to `0` when joining a channel, the SDK generates a random number for the user ID and returns the value in the `onJoinChannelSuccess` callback.
* **Channel media options**: Configure `ChannelMediaOptions` to define publishing and subscription settings, optimize performance for your specific use-case, and set optional parameters.
For Broadcast Streaming, set the `clientRoleType` to `clientRoleBroadcaster` (host) or `clientRoleAudience`, and the `audienceLatencyLevel` to `audienceLatencyLevelLowLatency`.
```dart
// Join a channel
Future _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,
// Set the audience latency level
audienceLatencyLevel: AudienceLatencyLevelType.audienceLatencyLevelLowLatency
),
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.
To ensure that you receive all Video SDK events, register the event handler before joining a channel.
### 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 _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 Broadcast Streaming.
```dart
Future _requestPermissions() async {
await [Permission.microphone, Permission.camera].request();
}
```
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 |
### Start and close the app [#start-and-close-the-app-4]
To start Broadcast Streaming, 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 Broadcast Streaming, leave the channel and release the engine instance.
```dart
// Leaves the channel and releases resources
Future _cleanupAgoraEngine() async {
await _engine.leaveChannel();
await _engine.release();
}
```
After you call `release`, you no longer have access to the methods and callbacks of the SDK. To use Broadcast Streaming features again, create a new engine instance.
### 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.
```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();
_startBroadcastStreaming();
}
// Initializes Agora SDK
Future _startBroadcastStreaming() async {
await _requestPermissions();
await _initializeAgoraVideoSDK();
await _setupLocalVideo();
_setupEventHandlers();
await _joinChannel();
}
// Requests microphone and camera permissions
Future _requestPermissions() async {
await [Permission.microphone, Permission.camera].request();
}
// Set up the Agora RTC engine instance
Future _initializeAgoraVideoSDK() async {
_engine = createAgoraRtcEngine();
await _engine.initialize(const RtcEngineContext(
appId: appId,
channelProfile: ChannelProfileType.channelProfileLiveBroadcasting,
));
}
// Enables and starts local video preview
Future _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 _joinChannel() async {
await _engine.joinChannel(
token: token,
channelId: channel,
options: const ChannelMediaOptions(
autoSubscribeVideo: true,
autoSubscribeAudio: true,
publishCameraTrack: true,
publishMicrophoneTrack: true,
clientRoleType: ClientRoleType.clientRoleBroadcaster,
audienceLatencyLevel: AudienceLatencyLevelType.audienceLatencyLevelLowLatency
),
uid: 0,
);
}
@override
void dispose() {
_cleanupAgoraEngine();
super.dispose();
}
// Leaves the channel and releases resources
Future _cleanupAgoraEngine() async {
await _engine.leaveChannel();
await _engine.release();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Agora Broadcast Streaming')),
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,
);
}
}
}
```
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.
### 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**
```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 app. 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:

## Reference [#reference-6]
This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.
* If a firewall is deployed in your network environment, refer to [Connect with Cloud Proxy](build/optimize-quality-and-connection/cloud-proxy.mdx) to use Agora services normally.
### Next steps [#next-steps-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](build/authenticate-users/use-tokens.mdx).
### 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-5]
* [How can I fix black screen issues?](/en/api-reference/faq/quality/video_blank)
* [Why can't I turn on the camera?](/en/api-reference/faq/quality/video_camera)
* [How can I listen for audience joining or leaving a channel?](/en/api-reference/faq/integration/audience_event)
* [How can I solve channel-related issues?](/en/api-reference/faq/integration/channel)
* [How can I set the log file?](/en/api-reference/faq/integration/set_log_file)
### See also [#see-also-6]
* [Error codes](reference/error-codes.md)
* [Connection status management](build/optimize-quality-and-connection/connection-status-management.mdx)
<_PlatformProcessedMarker close="true" />
<_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 Broadcast Streaming app using the Agora Video SDK.
## Understand the tech [#understand-the-tech-7]
To start a Broadcast Streaming session, implement the following steps in your app:
* **Initialize the Agora Engine**: Before calling other APIs, create and initialize an Agora Engine instance.
* **Join a channel**: Call methods to create and join a channel.
* **Join as a host**: A live streaming event has one or more hosts. A host publishes audio and video to the channel. Hosts can also subscribe to streams from other hosts.
* **Join as audience**: Audience members can only subscribe to streams published by hosts.
* **Send and receive audio and video**: Hosts publish streams to the channel. Audience members subscribe to audio and video streams published by hosts.

## Prerequisites [#prerequisites-7]
* React Native 0.60 or later. For more information, see [Get Started with React Native](https://reactnative.dev/docs/environment-setup).
* Node 16 or later
For your target platform, prepare the following:
Android
iOS
* A machine running macOS, Windows, or Linux
* [Java Development Kit (JDK) 11](https://openjdk.org/projects/jdk/11/) or later
* Latest version of Android Studio
* A physical or virtual mobile device running Android 5.0 or later
* A machine running macOS
* Xcode 10 or later
* CocoaPods
* A physical or virtual mobile device running iOS 9.0 or later.
If you use React Native 0.63 or later, ensure your iOS version is 10.0 or later.
* A camera and a microphone
* A valid Agora account and project. Please refer to [Agora account management](manage-agora-account.md) for details.
## Set up your project [#set-up-your-project-7]
This section shows you how to set up your React Native project and install the Agora Video SDK.
### Create a project [#create-a-project]
To create a new application using [React Native Community CLI](https://github.com/react-native-community/cli), see [Get Started Without a Framework](https://reactnative.dev/docs/getting-started-without-a-framework).
### Install the SDK [#install-the-sdk-7]
Download and add the Agora Video SDK to your React Native project. Choose either of the following methods:
**npm**
In your project folder, execute the following command:
```bash
npm i --save react-native-agora
```
**yarn**
In your project folder, execute the following commands:
```bash
# Install yarn
npm install -g yarn
# Use yarn to download Agora React Native SDK
yarn add react-native-agora
```
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
## Implement Broadcast Streaming [#implement-broadcast-streaming-7]
This section guides you through the implementation of basic real-time audio and video interaction in your app.
The following figure illustrates the essential steps:

This guide includes [complete sample code](#complete-sample-code) that demonstrates implementing basic real-time interaction. To understand the core API calls in the sample code, review the following implementation steps and use the code in your `App.tsx` file.
### Import dependencies [#import-dependencies-1]
Add the required Agora dependencies to your app.
```tsx
import {
createAgoraRtcEngine,
ChannelProfileType,
ClientRoleType,
AudienceLatencyLevelType,
IRtcEngine,
RtcSurfaceView,
RtcConnection,
IRtcEngineEventHandler,
} from 'react-native-agora';
```
### Initialize the engine [#initialize-the-engine-6]
For real-time communication, call `createAgoraRtcEngine` to create an `RtcEngine` instance, and then `initialize` the engine using `RtcEngineContext` to specify the [App ID](manage-agora-account.md).
```tsx
const appId = '<-- Insert app ID -->';
const setupVideoSDKEngine = async () => {
if (Platform.OS === 'android') { await getPermission(); }
agoraEngineRef.current = createAgoraRtcEngine();
const agoraEngine = agoraEngineRef.current;
await agoraEngine.initialize({ appId: appId });
};
```
### Join a channel [#join-a-channel-7]
To join a channel, call `joinChannel` with the following parameters:
* **Channel name**: The name of the channel to join. Clients that pass the same channel name join the same channel. If a channel with the specified name does not exist, it is created when the first user joins.
* **Authentication token**: A dynamic key that authenticates a user when the client joins a channel. In a production environment, you obtain a token from a [token server](build/authenticate-users/deploy-token-server.mdx) in your security infrastructure. For the purpose of this guide [Generate a temporary token](manage-agora-account.md).
* **User ID**: A 32-bit signed integer that identifies a user in the channel. You can specify a unique user ID for each user yourself. If you set the user ID to `0` when joining a channel, the SDK generates a random number for the user ID and returns the value in the `onJoinChannelSuccess` callback.
* **Channel media options**: Configure `ChannelMediaOptions` to define publishing and subscription settings, optimize performance for your specific use-case, and set optional parameters.
For Broadcast Streaming, set the `channelProfile` to `ChannelProfileLiveBroadcasting`, the `clientRoleType` to `ClientRoleBroadcaster` (host) or `ClientRoleAudience`, and the `audienceLatencyLevel` to `audienceLatencyLevelLowLatency`.
```tsx
const token = '<-- Insert token -->';
const channelName = '<-- Insert channel name -->';
const localUid = 0; // Local user UID, no need to modify
// Define the join method called after clicking the join channel button
const join = async () => {
if (isJoined) {
return;
}
if (isHost) {
// Join the channel as a broadcaster
agoraEngineRef.current?.joinChannel(token, channelName, localUid, {
// Set channel profile to live broadcast
channelProfile: ChannelProfileType.ChannelProfileLiveBroadcasting,
// Set user role to broadcaster
clientRoleType: ClientRoleType.ClientRoleBroadcaster,
// Publish audio collected by the microphone
publishMicrophoneTrack: true,
// Publish video collected by the camera
publishCameraTrack: true,
// Automatically subscribe to all audio streams
autoSubscribeAudio: true,
// Automatically subscribe to all video streams
autoSubscribeVideo: true,
});
} else {
// Join the channel as an audience
agoraEngineRef.current?.joinChannel(token, channelName, localUid, {
// Set channel profile to live broadcast
channelProfile: ChannelProfileType.ChannelProfileLiveBroadcasting,
// Set user role to audience
clientRoleType: ClientRoleType.ClientRoleAudience,
// Do not publish audio collected by the microphone
publishMicrophoneTrack: false,
// Do not publish video collected by the camera
publishCameraTrack: false,
// Automatically subscribe to all audio streams
autoSubscribeAudio: true,
// Automatically subscribe to all video streams
autoSubscribeVideo: true,',
// Change the delay level of the audience to achieve ultra-fast live broadcast
audienceLatencyLevel: AudienceLatencyLevelType.AudienceLatencyLevelLowLatency,
});
}
};
```
Since the Agora Engine runs locally, reloading the app using the Metro bundler may cause the engine reference to be lost, preventing channel joining. To resolve this issue, restart the app.
### 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);
};
```
To ensure that you receive all Video SDK events, register the event handler before joining a channel.
### 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
```
### 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
```
### Handle permissions [#handle-permissions-5]
To access the camera and microphone, ensure that the user grants the necessary permissions when the app starts.
Android
iOS
On Android devices, pop up a prompt box to obtain permission to use the camera and microphone.
```tsx
// Import components related to Android device permissions
import {PermissionsAndroid, Platform} from 'react-native';
const getPermission = async () => {
if (Platform.OS === 'android') {
await PermissionsAndroid.requestMultiple([
PermissionsAndroid.PERMISSIONS.RECORD_AUDIO,
PermissionsAndroid.PERMISSIONS.CAMERA,
]);
}
};
```
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. |
### Leave the channel [#leave-the-channel-2]
To exit a Broadcast Streaming channel, call `leaveChannel`.
```tsx
const leave = () => {
// Leave the channel
agoraEngineRef.current?.leaveChannel();
setIsJoined(false);
};
```
### Clean up resources [#clean-up-resources]
Before closing your app, unregister the event handler and call `release` to free up resources.
```tsx
const cleanupAgoraEngine = () => {
return () => {
agoraEngineRef.current?.unregisterEventHandler(eventHandler.current!);
agoraEngineRef.current?.release();
};
};
```
After calling `release,` methods and callbacks of the SDK are no longer available. To use the SDK functions again, create a new engine instance.
### 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.
```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 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(); // Implement callback functions
useEffect(() => {
const init = async () => {
await setupVideoSDKEngine();
setupEventHandler();
};
init();
return () => {
cleanupAgoraEngine(); // Ensure this is synchronous
};
}, []); // Empty dependency array ensures it runs only once
const setupEventHandler = () => {
eventHandler.current = {
onJoinChannelSuccess: () => {
setMessage('Successfully joined channel: ' + channelName);
setupLocalVideo();
setIsJoined(true);
},
onUserJoined: (_connection: RtcConnection, uid: number) => {
setMessage('Remote user ' + uid + ' joined');
setRemoteUid(uid);
},
onUserOffline: (_connection: RtcConnection, uid: number) => {
setMessage('Remote user ' + uid + ' left the channel');
setRemoteUid(uid);
},
};
agoraEngineRef.current?.registerEventHandler(eventHandler.current);
};
const setupVideoSDKEngine = async () => {
try {
if (Platform.OS === 'android') { await getPermission(); }
agoraEngineRef.current = createAgoraRtcEngine();
const agoraEngine = agoraEngineRef.current;
await agoraEngine.initialize({ appId: appId });
} catch (e) {
console.error(e);
}
};
const setupLocalVideo = () => {
agoraEngineRef.current?.enableVideo();
agoraEngineRef.current?.startPreview();
};
// Define the join method called after clicking the join channel button
const join = async () => {
if (isJoined) {
return;
}
try {
if (isHost) {
// Join the channel as a broadcaster
agoraEngineRef.current?.joinChannel(token, channelName, localUid, {
// Set channel profile to live broadcast
channelProfile: ChannelProfileType.ChannelProfileLiveBroadcasting,
// Set user role to broadcaster
clientRoleType: ClientRoleType.ClientRoleBroadcaster,
// Publish audio collected by the microphone
publishMicrophoneTrack: true,
// Publish video collected by the camera
publishCameraTrack: true,
// Automatically subscribe to all audio streams
autoSubscribeAudio: true,
// Automatically subscribe to all video streams
autoSubscribeVideo: true,
});
} else {
// Join the channel as an audience
agoraEngineRef.current?.joinChannel(token, channelName, localUid, {
// Set channel profile to live broadcast
channelProfile: ChannelProfileType.ChannelProfileLiveBroadcasting,
// Set user role to audience
clientRoleType: ClientRoleType.ClientRoleAudience,
// Do not publish audio collected by the microphone
publishMicrophoneTrack: false,
// Do not publish video collected by the camera
publishCameraTrack: false,
// Automatically subscribe to all audio streams
autoSubscribeAudio: true,
// Automatically subscribe to all video streams
autoSubscribeVideo: true,',
// Change the delay level of the audience to achieve ultra-fast live broadcast
audienceLatencyLevel: AudienceLatencyLevelType.AudienceLatencyLevelLowLatency,
});
}
} 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 (
Agora Video SDK Quickstart
Join Channel
Leave Channel
Audience {
setIsHost(switchValue);
if (isJoined) {
leave();
}
}}
value={isHost}
/>
Host
{isJoined && isHost ? (
Local user uid: {localUid}
) : (
Join a channel
)}
{isJoined && remoteUid !== 0 ? (
Remote user uid: {remoteUid}
) : (
{isJoined && !isHost ? 'Waiting for remote user to join' : ''}
)}
{message}
);
// 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;
```
### Create a user interface [#create-a-user-interface-7]
Design a user interface for your project based on your application use-case. A basic interface consists of two view frames to display local and remote videos. Add **Join channel** and **Leave channel** buttons to enable the user to join and leave a channel. To create such an interface, refer to the following code:
**Sample code to create the user interface**
```tsx
import React, { useState } from 'react';
// Import user interface elements
import {
SafeAreaView,
ScrollView,
StyleSheet,
Text,
View,
Switch,
} from 'react-native';
const localUid = 0; // Local user Uid, no need to modify
const App = () => {
const [isJoined, setIsJoined] = useState(false); // Whether the local user has joined the channel
const [isHost, setIsHost] = useState(true); // User role
const [remoteUid, setRemoteUid] = useState(0); // Uid of the remote user
const [message, setMessage] = useState(''); // User prompt message
// Render user interface
return (
Agora Video SDK Quickstart
Join Channel
Leave Channel
Audience {
setIsHost(switchValue);
if (isJoined) {
leave();
}
}}
value={isHost}
/>
Host
{isJoined && isHost ? (
Local user uid: {localUid}
) : (
Join a channel
)}
{isJoined && remoteUid !== 0 ? (
Remote user uid: {remoteUid}
) : (
{isJoined && !isHost ? 'Waiting for remote user to join' : ''}
)}
{message}
);
};
// 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.
Some simulators may not support all the functions of this project. Best practice is to run the project on a physical device.
Android
iOS
To run the app 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`.
To run on the app 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.
For detailed steps on running the app on a real Android or iOS device, please refer to [Running On Device](https://reactnative.dev/docs/running-on-device).
1. Launch the app and click the **Join channel** button to join a channel.
2. Invite a friend to install and run the app on a second device. Alternatively, use the [Web demo](https://webdemo.agora.io/basicVideoCall/index.html) to join the same channel. Once your friend joins the channel, you can see and hear each other.
## Reference [#reference-7]
This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.
* If a firewall is deployed in your network environment, refer to [Connect with Cloud Proxy](build/optimize-quality-and-connection/cloud-proxy.mdx) to use Agora services normally.
### Next steps [#next-steps-7]
After implementing the quickstart sample, read the following documents to learn more:
* To ensure communication security in a test or production environment, best practice is to obtain and use a token from an authentication server. For details, see [Secure authentication with tokens](build/authenticate-users/use-tokens.mdx).
### Sample project [#sample-project-7]
Agora provides open source sample projects on [GitHub](https://github.com/AgoraIO-Extensions/react-native-agora/blob/main/example/src/examples) for your reference. Download or view the [JoinChannelVideo](https://github.com/AgoraIO-Extensions/react-native-agora/blob/main/example/src/examples/basic/JoinChannelVideo) project for a more detailed example.
### API reference [#api-reference-7]
* [`createAgoraRtcEngine`](https://api-ref.agora.io/en/video-sdk/react-native/4.x/API/class_irtcengine.html#api_createagorartcengine)
* [`initialize`](https://api-ref.agora.io/en/video-sdk/react-native/4.x/API/class_irtcengine.html#api_irtcengine_initialize)
* [`registerEventHandler`](https://api-ref.agora.io/en/video-sdk/react-native/4.x/API/class_irtcengine.html#api_irtcengine_addhandler)
* [`setChannelProfile`](https://api-ref.agora.io/en/video-sdk/react-native/4.x/API/class_irtcengine.html#api_irtcengine_setchannelprofile)
* [`joinChannel`](https://api-ref.agora.io/en/video-sdk/react-native/4.x/API/class_irtcengine.html#api_irtcengine_joinchannel2)
* [`leaveChannel`](https://api-ref.agora.io/en/video-sdk/react-native/4.x/API/class_irtcengine.html#api_irtcengine_leavechannel2)
### Frequently asked questions [#frequently-asked-questions-6]
* [How can I fix black screen issues?](/en/api-reference/faq/quality/video_blank)
* [Why can't I turn on the camera?](/en/api-reference/faq/quality/video_camera)
* [How can I listen for audience joining or leaving a channel?](/en/api-reference/faq/integration/audience_event)
* [How can I solve channel-related issues?](/en/api-reference/faq/integration/channel)
* [How can I set the log file?](/en/api-reference/faq/integration/set_log_file)
### See also [#see-also-7]
* [Error codes](reference/error-codes.md)
* [Connection status management](build/optimize-quality-and-connection/connection-status-management.mdx)
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="javascript">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="javascript" />
This page provides a step-by-step guide on how to create a basic Broadcast Streaming app using the Agora Video SDK.
## Understand the tech [#understand-the-tech-8]
To start a Broadcast Streaming session, implement the following steps in your app:
* **Initialize the Agora Engine**: Before calling other APIs, create and initialize an Agora Engine instance.
* **Join a channel**: Call methods to create and join a channel.
* **Join as a host**: A live streaming event has one or more hosts. A host publishes audio and video to the channel. Hosts can also subscribe to streams from other hosts.
* **Join as audience**: Audience members can only subscribe to streams published by hosts.
* **Send and receive audio and video**: Hosts publish streams to the channel. Audience members subscribe to audio and video streams published by hosts.

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.
## Prerequisites [#prerequisites-8]
* A [supported browser](reference/supported-platforms.md).
* A JavaScript package manager such as [npm](https://www.npmjs.com/package/npm).
* A camera and a microphone
* A valid Agora account and project. Please refer to [Agora account management](manage-agora-account.md) for details.
## Set up your project [#set-up-your-project-8]
This section shows you how to set up your ReactJS project and install the Agora Video SDK.
### 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:
**NPM**
Navigate to the project folder and run the following command to install the Video SDK:
```bash
npm i agora-rtc-react
```
**CDN**
To integrate React JS dependencies and the Agora SDK using CDN, add the following code to your HTML file:
```html
```
## Implement Broadcast Streaming [#implement-broadcast-streaming-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:
```tsx
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";
import AgoraRTC, { AgoraRTCProvider } from "agora-rtc-react";
```
### Initialize the client [#initialize-the-client]
Use the `createClient` method provided by the SDK to create a client object for the `` component. Wrap the `` component with `` to enable state management and access operation-related hooks.
For Broadcast Streaming, set the `mode` property of `ClientConfig` to `"live"`, the client `role` to `"host"` or `"audience"` and the audience latency `level` to `1`.
```tsx
const client = AgoraRTC.createClient({ mode: "live", codec: "vp8" });
// Set the client role and latency level
// Join as audience
client.setClientRole("audience", { level: 1 });
// To join as a host, use:
// client.setClientRole("host");
return(
);
}
```
### Join a channel [#join-a-channel-8]
To join a channel, use the `useJoin` hook. You can specify the following `joinOptions`:
* **App ID**: [`appid`](manage-agora-account.md) 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](build/authenticate-users/deploy-token-server.mdx) in your security infrastructure. For the purpose of this guide [Generate a temporary token](manage-agora-account.md).
* **User ID**: `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`:
```tsx
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`:
```tsx
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`:
```tsx
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:
```tsx
```
### 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:
```tsx
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:
```tsx
{remoteUsers.map((user) => (
{user.uid}
))}
```
### 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.
```tsx
import {
LocalUser,
RemoteUser,
useIsConnected,
useJoin,
useLocalMicrophoneTrack,
useLocalCameraTrack,
usePublish,
useRemoteUsers,
} from "agora-rtc-react";
import { useState } from "react";
import AgoraRTC, { AgoraRTCProvider } from "agora-rtc-react";
const client = AgoraRTC.createClient({ mode: "live", codec: "vp8" });
return(
);
}
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 (
<>
)}
>
);
};
export default BroadcastStreaming;
```
## Test the sample code [#test-the-sample-code-8]
Use [CodeSandbox](https://codesandbox.io/), to swiftly run the complete demo code and experience the React Video SDK functionality in just a few simple steps.
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.
If `CodeSandbox` access is slow, try adjusting your network configuration.
## Reference [#reference-8]
This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.
* If a firewall is deployed in your network environment, refer to [Connect with Cloud Proxy](build/optimize-quality-and-connection/cloud-proxy.mdx) to use Agora services normally.
### Next steps [#next-steps-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](build/authenticate-users/use-tokens.mdx).
### 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-8]
* [Error codes](reference/error-codes.md)
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="unity">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="unity" />
This page provides a step-by-step guide on how to create a basic Broadcast Streaming app using the Agora Video SDK.
## Understand the tech [#understand-the-tech-9]
To start a Broadcast Streaming session, implement the following steps in your app:
* **Initialize the Agora Engine**: Before calling other APIs, create and initialize an Agora Engine instance.
* **Join a channel**: Call methods to create and join a channel.
* **Join as a host**: A live streaming event has one or more hosts. A host publishes audio and video to the channel. Hosts can also subscribe to streams from other hosts.
* **Join as audience**: Audience members can only subscribe to streams published by hosts.
* **Send and receive audio and video**: Hosts publish streams to the channel. Audience members subscribe to audio and video streams published by hosts.

## Prerequisites [#prerequisites-9]
* [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 |
* A camera and a microphone
* A valid Agora account and project. Please refer to [Agora account management](manage-agora-account.md) for details.
## Set up your project [#set-up-your-project-9]
This section shows you how to set up your Unity project and install the Agora Video SDK.
**Create a new project**
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**.
**Add to an existing project**
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.
### Install the SDK [#install-the-sdk-9]
1. Go to the [Download SDKs](/en/api-reference/sdks?product=video\&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 Broadcast Streaming [#implement-broadcast-streaming-9]
This section guides you through the implementation of basic real-time audio and video interaction in your game.
The following figure illustrates the essential steps:

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 Broadcast Streaming and bind the script to the canvas.
**Steps to set up a script**
1. Create a new script and import the UI library.
2. 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.
3. Rename the file to `JoinChannel.cs` and open it.
4. Import the Unity namespaces to access UI components by adding the following code at the top of the file:
```csharp
using UnityEngine;
using UnityEngine.UI;
```
5. 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.
```csharp
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:
```csharp
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_LIVE_BROADCASTING;
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](build/authenticate-users/deploy-token-server.mdx) in your security infrastructure. For the purpose of this guide [Generate a temporary token](manage-agora-account.md).
* **User ID**: A 32-bit signed integer that identifies a user in the channel. You can specify a unique user ID for each user yourself. If you set the user ID to `0` when joining a channel, the SDK generates a random number for the user ID and returns the value in the `OnJoinChannelSuccess` callback.
* **Channel media options**: Configure `ChannelMediaOptions` to define publishing and subscription settings, optimize performance for your specific use-case, and set optional parameters.
For Broadcast Streaming, set the `channelProfile` to `CHANNEL_PROFILE_LIVE_BROADCASTING`, the `clientRoleType` to `CLIENT_ROLE_BROADCASTER` (host) or `CLIENT_ROLE_AUDIENCE`, and the `audienceLatencyLevel` to `AUDIENCE_LATENCY_LEVEL_LOW_LATENCY`.
```csharp
// 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();
// Start video rendering
LocalView.SetEnable(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 broadcast
options.channelProfile.SetValue(CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_LIVE_BROADCASTING);
//Set the user role as host
options.clientRoleType.SetValue(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
// Set the audience latency level
options.audienceLatencyLevel.SetValue(AUDIENCE_LATENCY_LEVEL_TYPE.AUDIENCE_LATENCY_LEVEL_LOW_LATENCY);
// Join a 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.
```csharp
// 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);
}
```
To ensure that you receive all Video SDK events, register the event handler before joining a channel.
### 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.
```csharp
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.
```csharp
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 camera and microphone, add device permissions to your project according to your target platform.
**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:
```csharp
#if (UNITY_2018_3_OR_NEWER && UNITY_ANDROID)
using UnityEngine.Android;
#endif
```
2. Create a list of permissions to be obtained.
```csharp
#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.
```csharp
private void CheckPermissions() {
#if (UNITY_2018_3_OR_NEWER && UNITY_ANDROID)
foreach (string permission in permissionList) {
if (!Permission.HasUserAuthorizedPermission(permission)) {
Permission.RequestUserPermission(permission);
}
}
#endif
}
```
**iOS and macOS**
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.
### Start and stop your game [#start-and-stop-your-game]
1. When the game starts, ensure that device permissions have been granted.
```csharp
void Update() {
CheckPermissions();
}
```
2. To start Broadcast Streaming, initialize the engine and set up the event handler.
```csharp
void Start()
{
SetupVideoSDKEngine();
InitEventHandler();
PreviewSelf();
}
```
3. To clean up all session-related resources when a user exits the game, call the `Dispose` method of the `IRtcEngine`.
```csharp
void OnApplicationQuit() {
if (RtcEngine != null) {
Leave();
// Destroy IRtcEngine
RtcEngine.Dispose();
RtcEngine = null;
}
}
```
After calling `Dispose`, you can no longer use any methods or callbacks of the SDK. To use Broadcast Streaming features again, create a new engine instance.
### 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 Broadcast Streaming in your game**
```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();
go.transform.Rotate(0.0f, 0.0f, -180.0f);
go = GameObject.Find("RemoteView");
RemoteView = go.AddComponent();
go.transform.Rotate(0.0f, 0.0f, -180.0f);
go = GameObject.Find("Leave");
go.GetComponent
### 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**
1. Create buttons to join and leave channel
2. In your Unity project, right-click the **Sample Scene** and select **Game Object > UI > Button**. You see a button on the scene canvas.
3. In the **Inspector** panel, rename the button to `Join` and adjust the position coordinates as needed. For example:
* **Pos X**:`-329`
* **Pos Y**: `-172`
4. Select the **Text** control of the **Join** button , and change the text to `Join` in the **Inspector** panel.
5. Repeat the steps to create a **Leave** button, using the following positions:
* **Pos X**:`329`
* **Pos Y**: `-172`
6. Create local and remote view windows
7. Right-click the Canvas and select **UI > Raw Image**.
8. 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`
9. 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:

## 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 game 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 to this product.
* If a firewall is deployed in your network environment, refer to [Connect with Cloud Proxy](build/optimize-quality-and-connection/cloud-proxy.mdx) to use Agora services normally.
### Next steps [#next-steps-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](build/authenticate-users/use-tokens.mdx).
### 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-7]
* [How can I fix black screen issues?](/en/api-reference/faq/quality/video_blank)
* [Why can't I turn on the camera?](/en/api-reference/faq/quality/video_camera)
* [How can I listen for audience joining or leaving a channel?](/en/api-reference/faq/integration/audience_event)
* [How can I solve channel-related issues?](/en/api-reference/faq/integration/channel)
### See also [#see-also-9]
* [Error codes](reference/error-codes.md)
* [Connection status management](build/optimize-quality-and-connection/connection-status-management.mdx)
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="unreal">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="unreal" />
This page provides a step-by-step guide on how to create a basic Broadcast Streaming app using the Agora Video SDK.
## Understand the tech [#understand-the-tech-10]
To start a Broadcast Streaming session, implement the following steps in your app:
* **Initialize the Agora Engine**: Before calling other APIs, create and initialize an Agora Engine instance.
* **Join a channel**: Call methods to create and join a channel.
* **Join as a host**: A live streaming event has one or more hosts. A host publishes audio and video to the channel. Hosts can also subscribe to streams from other hosts.
* **Join as audience**: Audience members can only subscribe to streams published by hosts.
* **Send and receive audio and video**: Hosts publish streams to the channel. Audience members subscribe to audio and video streams published by hosts.

## Prerequisites [#prerequisites-10]
* 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 only supports Unreal Engine 4 and below. To use Unreal Engine with 32-bit Windows, uncomment the code relating to `Win32` in the `AgoraPluginLibrary.Build.cs` file. |
* Two physical devices for testing
* A camera and a microphone
* A valid Agora account and project. Please refer to [Agora account management](manage-agora-account.md) for details.
## Set up your project [#set-up-your-project-10]
This section shows you how to set up your Unreal Engine project and install the Agora Video SDK.
**Create a new project**
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 **C++**.
* **Target Platform**: Select **Desktop**.
* **Project Location**: Enter the project files storage path.
* **Project Name**: Type a suitable name for your project.
Click **Create**.

**Add to an existing project**
1. In the Unreal Project Browser, click on **Browse** and locate the `.uproject` file.
2. Select the project and click **Open**.
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**.

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 Unreal Engine Video SDK to your project:
1. Download the latest version of Agora Unreal Video SDK from [Download SDKs](/en/api-reference/sdks?product=video\&platform=unreal-engine) and unzip it.
2. In your project root folder, create a `Plugins` folder.
3. Copy `AgoraPlugin` from the Unreal SDK folder to `Plugins`.
## Implement Broadcast Streaming [#implement-broadcast-streaming-10]
This section guides you through the implementation of basic real-time audio and video interaction in your game.
The following figure illustrates the essential steps:

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](manage-agora-account.md), and custom [event handler](#subscribe-to--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](build/authenticate-users/deploy-token-server.mdx) in your security infrastructure. For the purpose of this guide [Generate a temporary token](manage-agora-account.md).
* **User ID**: A 32-bit signed integer that identifies a user in the channel. You can specify a unique user ID for each user yourself. If you set the user ID to `0` when joining a channel, the SDK generates a random number for the user ID and returns the value in the `onJoinChannelSuccess` callback.
* **Channel media options**: Configure `ChannelMediaOptions` to define publishing and subscription settings, optimize performance for your specific use-case, and set optional parameters.
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 Broadcast Streaming, set the `channelProfile` to `CHANNEL_PROFILE_LIVE_BROADCASTING`, the `clientRoleType` to `CLIENT_ROLE_BROADCASTER` (host) or `CLIENT_ROLE_AUDIENCE`, and the `audienceLatencyLevel` to `AUDIENCE_LATENCY_LEVEL_LOW_LATENCY`.
```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_LIVE_BROADCASTING;
// Set the user role as host or audience
options.clientRoleType = agora::rtc::CLIENT_ROLE_TYPE::CLIENT_ROLE_BROADCASTER;
// Set the latency level for optimal experience
options.audienceLatencyLevel = agora::rtc::AUDIENCE_LATENCY_LEVEL_TYPE::AUDIENCE_LATENCY_LEVEL_LOW_LATENCY;
// 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 camera and microphone, follow the steps for your target platform:
**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 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
}
```
**iOS and macOS**
1. Refer to [How to add the permissions required for real-time interaction to an Unreal Engine project?](index.mdx)
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" } );
}
}
```
### 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 game, 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`**
```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`**
```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_LIVE_BROADCASTING;
options.clientRoleType = agora::rtc::CLIENT_ROLE_TYPE::CLIENT_ROLE_BROADCASTER;
options.audienceLatencyLevel = agora::rtc::AUDIENCE_LATENCY_LEVEL_TYPE::AUDIENCE_LATENCY_LEVEL_LOW_LATENCY;
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`**
```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
}
}
```
### 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**
1. Create **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.

2. Create a view canvas.
Select **Palette > PANEL > Canvas Panel** and drag it to **AgoraWidget**.

3. Create join and leave channel buttons in Widget Blueprint
4. 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`
5. 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.
6. Repeat the above steps to create a **LeaveBtn**. Adjust the button size and position according to your layout design.
7. Create local and remote views in the Widget Blueprint
8. Select **COMMON > Image**, drag it to the **Canvas Panel**, 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`
9. 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`
10. Save the changes. Your user interface in **Widget Blueprint** looks similar to the following:

11. Create a **Level Blueprint** and associate it with the created **Widget Blueprint**
12. In **Unreal Editor**, click **Content Drawer**, right-click to select **Level**, and name it **agoraLevel**.
13. Double-click to open **agoraLevel** and click **Open Level Blueprint**.

14. Right-click and enter **Create Widget** in the search box. Select the created **AgoraWidget**, create **Event BeginPlay** and **Add to Viewport** in the same way. Connect them as follows:

15. Save your changes and run the project. The UI you have created looks similar to the following:

## 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 game 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 to this product.
* If a firewall is deployed in your network environment, refer to [Connect with Cloud Proxy](build/optimize-quality-and-connection/cloud-proxy.mdx) to use Agora services normally.
### Next steps [#next-steps-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](build/authenticate-users/use-tokens.mdx).
### 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/quickstart).
### API reference [#api-reference-10]
* [`JoinChannel`](https://api-ref.agora.io/en/video-sdk/unreal-engine/4.x/API/class_irtcengine.html#api_irtcengine_joinchannel)
* [`EnableVideo`](https://api-ref.agora.io/en/video-sdk/unreal-engine/4.x/API/class_irtcengine.html#api_irtcengine_enablevideo)
* [`CreateAgoraRtcEngine`](https://api-ref.agora.io/en/video-sdk/unreal-engine/4.x/API/class_irtcengine.html#api_createagorartcengine)
* [`SetClientRole`](https://api-ref.agora.io/en/video-sdk/unreal-engine/4.x/API/class_irtcengine.html#api_irtcengine_setclientrole)
* [`LeaveChannel`](https://api-ref.agora.io/en/video-sdk/unreal-engine/4.x/API/class_irtcengine.html#api_irtcengine_leavechannel)
* [`DisableVideo`](https://api-ref.agora.io/en/video-sdk/unreal-engine/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)
* [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-10]
* [Error codes](reference/error-codes.md)
* [Connection status management](build/optimize-quality-and-connection/connection-status-management.mdx)
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="blueprint">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="blueprint" />
This page provides a step-by-step guide on how to create a basic Broadcast Streaming app using the Agora Video SDK.
## Understand the tech [#understand-the-tech-11]
To start a Broadcast Streaming session, implement the following steps in your app:
* **Initialize the Agora Engine**: Before calling other APIs, create and initialize an Agora Engine instance.
* **Join a channel**: Call methods to create and join a channel.
* **Join as a host**: A live streaming event has one or more hosts. A host publishes audio and video to the channel. Hosts can also subscribe to streams from other hosts.
* **Join as audience**: Audience members can only subscribe to streams published by hosts.
* **Send and receive audio and video**: Hosts publish streams to the channel. Audience members subscribe to audio and video streams published by hosts.

## Prerequisites [#prerequisites-11]
* 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 only supports Unreal Engine 4 and below. To use Unreal Engine with 32-bit Windows, uncomment the code relating to `Win32` in the `AgoraPluginLibrary.Build.cs` file. |
* Two physical devices for testing
* A camera and a microphone
* A valid Agora account and project. Please refer to [Agora account management](manage-agora-account.md) for details.
## Set up your project [#set-up-your-project-11]
This section shows you how to set up your Unreal (Blueprint) project and install the Agora Video SDK.
**Create a new project**
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**.

**Add to an existing project**
1. In the Unreal Project Browser, click on **Browse** and locate the `.uproject` file.
2. Select the project and click **Open**.
### Install the SDK [#install-the-sdk-11]
Take the following steps to add the Unreal (Blueprint) Video SDK to your project:
1. Download the latest version of Agora Unreal Video SDK from [Download SDKs](/en/api-reference/sdks?product=video\&platform=unreal-engine) and unzip it.
2. In your project root folder, create a `Plugins` folder.
3. Copy `AgoraPlugin` from the Unreal SDK folder to `Plugins`.
## Implement Broadcast Streaming [#implement-broadcast-streaming-11]
This section guides you through the implementation of basic real-time audio and video interaction in your game.
### 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.

### 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 Broadcast Streaming logic:

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.
This node is available on Windows and macOS only. If the node is not retrieved at creation time, uncheck **Context Sensitive**. 
|
| 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.
If your target platform is Android, create this node to check system permissions.
|
| 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.
\*\* 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.

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

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)
1. To initialize the RTC engine, in the **InitRtcEngine** function, create and connect nodes as shown in the following figure:

2. 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.
3. Bind `IRtcEngineEventHandler` class-related callback functions.
4. 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`. |
1. Create a **Bind Event** function. In this function, add a **Sequence** node, and then bind the `onJoinChannelSuccess`, `onLeaveChannel`, `onUserJoined`, and `onUserOffline` callback events.

2. `IRtcEngine` initialization
3. Call **Initialize** to initialize the RTC engine.
4. Connect to the **RtcEngineContext** configuration `IRtcEngine` instance and select **Channel Profile** as `CHANNEL_PROFILE_LIVE_BROADCASTING`.
### Bind UI events [#bind-ui-events]
To bind UI events:
1. Create and implement the `OnJoinChannelClicked` event callback.

2. Call **Enable Video** and **Enable Audio** to enable the video and audio modules.
3. Call **Join Channel** to join the channel.
4. 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`.
5. Create and implement the `OnLeaveChannelClicked` event callback. When the event is triggered, call **Leave Channel** to leave the channel.

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

You can also bind UI events in Unreal Motion Graphics (UMG). This document only shows binding using the **Bind UIEvent Function**.
### 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:

2. 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.
3. 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).
4. 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).
5. Create and implement the **ReleaseVideoView** function to release the view when a local or remote user leaves the channel.

6. 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.
7. Release the local view.
8. 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):

2. After the local user leaves the channel, the `onLeaveChannel` callback is triggered which releases the local view:

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):

4. When a remote user leaves the channel, the `onUserOffline` callback is triggered to release the remote view:

### 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 SDRTN® event callback.
3. Destroy `IRtcEngineObject` to release all resources used by Agora SDK.
Refer to the figure below to implement the `UnInitRtcEngine` function:

## 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 game 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 to this product.
* If a firewall is deployed in your network environment, refer to [Connect with Cloud Proxy](build/optimize-quality-and-connection/cloud-proxy.mdx) to use Agora services normally.
### Next steps [#next-steps-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](build/authenticate-users/use-tokens.mdx).
### 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-9]
* [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)
* [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-11]
* [Error codes](reference/error-codes.md)
* [Connection status management](build/optimize-quality-and-connection/connection-status-management.mdx)
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="python">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="python" />
This page provides a step-by-step guide on how to create a basic Broadcast Streaming app using the Agora Video SDK.
## Understand the tech [#understand-the-tech-12]
To start a Broadcast Streaming session, implement the following steps in your app:
* **Initialize the Agora Engine**: Before calling other APIs, create and initialize an Agora Engine instance.
* **Join a channel**: Call methods to create and join a channel.
* **Join as a host**: A live streaming event has one or more hosts. A host publishes audio and video to the channel. Hosts can also subscribe to streams from other hosts.
* **Join as audience**: Audience members can only subscribe to streams published by hosts.
* **Send and receive audio and video**: Hosts publish streams to the channel. Audience members subscribe to audio and video streams published by hosts.

## Prerequisites [#prerequisites-12]
* A camera and a microphone
* A valid Agora account and project. Please refer to [Agora account management](manage-agora-account.md) for details.
## Set up your project [#set-up-your-project-12]
This section shows you how to set up your Python project and install the Agora Video SDK.
1. Install the build tools for compiling the SDK.
```text
sudo apt install build-essential python3-dev
```
2. To implement structured, asynchronous event handling in Python, install the `pyee` library.
```bash
pip3 install pyee
```
3. Install the Agora server side Python SDK.
```bash
pip3 install agora-python-server-sdk
```
The Python SDK is a server side SDK.
## Implement Broadcast Streaming [#implement-broadcast-streaming-12]
This section guides you through the implementation of basic real-time audio and video interaction in your app.
### Import Agora classes [#import-agora-classes-2]
Import the relevant Agora SDK classes and interfaces:
```python
from agora.rtc.agora_base import (
AudioScenarioType,
ChannelProfileType,
ClientRoleType,
)
from agora.rtc.agora_service import (
AgoraService,
AgoraServiceConfig,
RTCConnConfig,
)
from agora.rtc.audio_frame_observer import AudioFrame, IAudioFrameObserver
from agora.rtc.audio_pcm_data_sender import PcmAudioFrame
from agora.rtc.local_user import LocalUser
from agora.rtc.local_user_observer import IRTCLocalUserObserver
from agora.rtc.rtc_connection import RTCConnection, RTCConnInfo
from agora.rtc.rtc_connection_observer import IRTCConnectionObserver
```
### Initialize the engine [#initialize-the-engine-9]
The following code defines the `RtcEngine` class, which initializes and configures the `AgoraService`. The class constructor takes an `appid` as input, configures the Agora service, and initializes it. You use the `RtcEngine` class to interact with the Agora SDK in this demo.
```python
class RtcEngine:
def __init__(self, appid: str, appcert: str):
self.appid = appid
self.appcert = appcert
if not appid:
raise Exception("App ID is required)")
config = AgoraServiceConfig()
config.audio_scenario = AudioScenarioType.AUDIO_SCENARIO_CHORUS
config.appid = appid
config.log_path = os.path.join(
os.path.dirname(
os.path.dirname(
os.path.dirname(os.path.join(os.path.abspath(__file__)))
)
),
"agorasdk.log",
)
self.agora_service = AgoraService()
self.agora_service.initialize(config)
```
### Join a channel [#join-a-channel-11]
To asynchronously join a channel, implement a `Channel` class. When you create an instance of the class, the initializer sets up the necessary components for joining a channel. It takes an instance of `RtcEngine`, a `channelId`, and a `uid` as parameters. During initialization, the code creates an event emitter, configures the connection for broadcasting, and registers an event observer for channel events. It also sets up the local user’s audio configuration to enable audio streaming.
UIDs in the Python SDK are set using a string value. Agora recommends using only numerical values for UID strings to ensure compatibility with all Agora products and extensions.
```python
class Channel:
def __init__(self, rtc: "RtcEngine", options: RtcOptions) -> None:
self.loop = asyncio.get_event_loop()
# Create the event emitter
self.emitter = AsyncIOEventEmitter(self.loop)
self.connection_state = 0
self.options = options
self.remote_users = dict[int, Any]()
self.rtc = rtc
self.chat = Chat(self)
self.channelId = options.channel_name
self.uid = options.uid
self.enable_pcm_dump = options.enable_pcm_dump
self.token = options.build_token(rtc.appid, rtc.appcert) if rtc.appcert else ""
conn_config = RTCConnConfig(
client_role_type=ClientRoleType.CLIENT_ROLE_BROADCASTER,
channel_profile=ChannelProfileType.CHANNEL_PROFILE_LIVE_BROADCASTING,
)
self.connection = self.rtc.agora_service.create_rtc_connection(conn_config)
self.channel_event_observer = ChannelEventObserver(
self.emitter,
options=options,
)
self.connection.register_observer(self.channel_event_observer)
self.local_user = self.connection.get_local_user()
self.local_user.set_playback_audio_frame_before_mixing_parameters(
options.channels, options.sample_rate
)
self.local_user.register_local_user_observer(self.channel_event_observer)
self.local_user.register_audio_frame_observer(self.channel_event_observer)
# self.local_user.subscribe_all_audio()
self.media_node_factory = self.rtc.agora_service.create_media_node_factory()
self.audio_pcm_data_sender = (
self.media_node_factory.create_audio_pcm_data_sender()
)
self.audio_track = self.rtc.agora_service.create_custom_audio_track_pcm(
self.audio_pcm_data_sender
)
self.audio_track.set_enabled(1)
self.local_user.publish_audio(self.audio_track)
self.stream_id = self.connection.create_data_stream(False, False)
self.received_chunks = {}
self.waiting_message = None
self.msg_id = ""
self.msg_index = ""
self.on(
"user_joined",
lambda agora_rtc_conn, user_id: self.remote_users.update({user_id: True}),
)
self.on(
"user_left",
lambda agora_rtc_conn, user_id, reason: self.remote_users.pop(
user_id, None
),
)
```
The following code uses the `Channel` class to join a channel. It sets up a `future` to handle the connection state and returns a `Channel` object when the connection is successfully established.
```python
async def connect(self) -> None:
"""
Connects to a channel.
Parameters:
channelId: The channel ID.
uid: The user ID.
Returns:
Channel: The connected channel.
"""
if self.connection_state == 3:
return
future = asyncio.Future()
def callback(agora_rtc_conn: RTCConnection, conn_info: RTCConnInfo, reason):
logger.info(f"Connection state changed: {conn_info.state}")
if conn_info.state == 3: # Connection successful
future.set_result(None)
elif conn_info.state == 5: # Connection failed
future.set_exception(
Exception(f"Connection failed with state: {conn_info.state}")
)
self.on("connection_state_changed", callback)
logger.info(f"Connecting to channel {self.channelId} with token {self.token}")
self.connection.connect(self.token, self.channelId, f"{self.uid}")
if self.enable_pcm_dump:
agora_parameter = self.connection.get_agora_parameter()
agora_parameter.set_parameters("{\"che.audio.frame_dump\":{\"location\":\"all\",\"action\":\"start\",\"max_size_bytes\":\"120000000\",\"uuid\":\"123456789\",\"duration\":\"1200000\"}}")
try:
await future
except Exception as e:
raise Exception(
f"Failed to connect to channel {self.channelId}: {str(e)}"
) from e
finally:
self.off("connection_state_changed", callback)
```
### Handle connection and channel events [#handle-connection-and-channel-events]
To listen for channel and connection events, such as users joining or leaving the channel, and connection state changes, implement the `ChannelEventObserver` class. This class enables you to respond to SDK events.
```python
class ChannelEventObserver(
IRTCConnectionObserver, IRTCLocalUserObserver, IAudioFrameObserver
):
def __init__(self, event_emitter: AsyncIOEventEmitter, options: RtcOptions) -> None:
self.loop = asyncio.get_event_loop()
self.emitter = event_emitter
self.audio_streams = dict[int, AudioStream]()
self.options = options
def emit_event(self, event_name: str, *args):
"""Helper function to emit events."""
self.loop.call_soon_threadsafe(self.emitter.emit, event_name, *args)
def on_connected(
self, agora_rtc_conn: RTCConnection, conn_info: RTCConnInfo, reason
):
logger.info(f"Connected to RTC: {agora_rtc_conn} {conn_info} {reason}")
self.emit_event("connection_state_changed", agora_rtc_conn, conn_info, reason)
def on_disconnected(
self, agora_rtc_conn: RTCConnection, conn_info: RTCConnInfo, reason
):
logger.info(f"Disconnected from RTC: {agora_rtc_conn} {conn_info} {reason}")
self.emit_event("connection_state_changed", agora_rtc_conn, conn_info, reason)
def on_connecting(
self, agora_rtc_conn: RTCConnection, conn_info: RTCConnInfo, reason
):
logger.info(f"Connecting to RTC: {agora_rtc_conn} {conn_info} {reason}")
self.emit_event("connection_state_changed", agora_rtc_conn, conn_info, reason)
def on_connection_failure(self, agora_rtc_conn, conn_info, reason):
logger.error(f"Connection failure: {agora_rtc_conn} {conn_info} {reason}")
self.emit_event("connection_state_changed", agora_rtc_conn, conn_info, reason)
def on_user_joined(self, agora_rtc_conn: RTCConnection, user_id):
logger.info(f"User joined: {agora_rtc_conn} {user_id}")
self.emit_event("user_joined", agora_rtc_conn, user_id)
def on_user_left(self, agora_rtc_conn: RTCConnection, user_id, reason):
logger.info(f"User left: {agora_rtc_conn} {user_id} {reason}")
self.emit_event("user_left", agora_rtc_conn, user_id, reason)
def handle_received_chunk(self, json_chunk):
chunk = json.loads(json_chunk)
msg_id = chunk["msg_id"]
part_idx = chunk["part_idx"]
total_parts = chunk["total_parts"]
if msg_id not in self.received_chunks:
self.received_chunks[msg_id] = {"parts": {}, "total_parts": total_parts}
if (
part_idx not in self.received_chunks[msg_id]["parts"]
and 0 <= part_idx < total_parts
):
self.received_chunks[msg_id]["parts"][part_idx] = chunk
if len(self.received_chunks[msg_id]["parts"]) == total_parts:
# all parts received, now recomposing original message and get rid it from dict
sorted_parts = sorted(
self.received_chunks[msg_id]["parts"].values(),
key=lambda c: c["part_idx"],
)
full_message = "".join(part["content"] for part in sorted_parts)
del self.received_chunks[msg_id]
return full_message, msg_id
return (None, None)
def on_stream_message(
self, agora_local_user: LocalUser, user_id, stream_id, data, length
):
# logger.info(f"Stream message", agora_local_user, user_id, stream_id, length)
(reassembled_message, msg_id) = self.handle_received_chunk(data)
if reassembled_message is not None:
logger.info(f"Reassembled message: {msg_id} {reassembled_message}")
def on_audio_subscribe_state_changed(
self,
agora_local_user,
channel,
user_id,
old_state,
new_state,
elapse_since_last_state,
):
logger.info(
f"Audio subscribe state changed: {user_id} {new_state} {elapse_since_last_state}"
)
self.emit_event(
"audio_subscribe_state_changed",
agora_local_user,
channel,
user_id,
old_state,
new_state,
elapse_since_last_state,
)
def on_playback_audio_frame_before_mixing(
self, agora_local_user: LocalUser, channelId, uid, frame: AudioFrame
):
audio_frame = PcmAudioFrame()
audio_frame.samples_per_channel = frame.samples_per_channel
audio_frame.bytes_per_sample = frame.bytes_per_sample
audio_frame.number_of_channels = frame.channels
audio_frame.sample_rate = self.options.sample_rate
audio_frame.data = frame.buffer
self.loop.call_soon_threadsafe(
self.audio_streams[uid].queue.put_nowait, audio_frame
)
return 0
```
### Subscribe to an audio stream [#subscribe-to-an-audio-stream]
To asynchronously subscribe to audio streams for a specific user identified by their `uid`, refer to the following code. The method sets up a callback to monitor changes in the audio subscription state and handles the result based on whether the subscription is successfully established.
```python
async def subscribe_audio(self, uid: int) -> None:
"""
Subscribes to the audio of a user.
Parameters:
uid: The user ID to subscribe to.
"""
future = asyncio.Future()
def callback(
agora_local_user,
channel,
user_id,
old_state,
new_state,
elapse_since_last_state,
):
if new_state == 3: # Successfully subscribed
future.set_result(None)
self.on("audio_subscribe_state_changed", callback)
self.local_user.subscribe_audio(uid)
try:
await future
except Exception as e:
raise Exception(
f"Audio subscription failed for user {uid}: {str(e)}"
) from e
finally:
self.off("audio_subscribe_state_changed", callback)
```
### Unsubscribe from an audio stream [#unsubscribe-from-an-audio-stream]
To unsubscribe from an audio stream, implement an asynchronous method similar to `subscribe_audio` and use the following code to unsubscribe:
```python
self.local_user.unsubscribe_audio(uid)
```
### Disconnect from the service [#disconnect-from-the-service]
To leave a channel, disconnect from Agora SDRTN® and release resources, refer to he following code.
```python
async def disconnect(self) -> None:
"""
Disconnects the channel.
"""
if self.connection_state == 1:
return
disconnected_future = asyncio.Future[None]()
def callback(agora_rtc_conn: RTCConnection, conn_info: RTCConnInfo, reason):
self.off("connection_state_changed", callback)
if conn_info.state == 1:
disconnected_future.set_result(None)
self.on("connection_state_changed", callback)
self.connection.disconnect()
await disconnected_future
```
### Complete code [#complete-code]
The `rtc.py` script integrates the code components presented in this section into reusable Python classes that you can extend for your own applications.
**Complete code for `rtc.py`**
```python
import asyncio
import json
import logging
import os
from typing import Any, AsyncIterator
from agora.rtc.agora_base import (
AudioScenarioType,
ChannelProfileType,
ClientRoleType,
)
from agora.rtc.agora_service import (
AgoraService,
AgoraServiceConfig,
RTCConnConfig,
)
from agora.rtc.audio_frame_observer import AudioFrame, IAudioFrameObserver
from agora.rtc.audio_pcm_data_sender import PcmAudioFrame
from agora.rtc.local_user import LocalUser
from agora.rtc.local_user_observer import IRTCLocalUserObserver
from agora.rtc.rtc_connection import RTCConnection, RTCConnInfo
from agora.rtc.rtc_connection_observer import IRTCConnectionObserver
from pyee.asyncio import AsyncIOEventEmitter
from .logger import setup_logger
from .token_builder.realtimekit_token_builder import RealtimekitTokenBuilder
# Set up the logger with color and timestamp support
logger = setup_logger(name=__name__, log_level=logging.INFO)
class RtcOptions:
def __init__(
self,
*,
channel_name: str = None,
uid: int = 0,
sample_rate: int = 24000,
channels: int = 1,
enable_pcm_dump: bool = False,
):
self.channel_name = channel_name
self.uid = uid
self.sample_rate = sample_rate
self.channels = channels
self.enable_pcm_dump = enable_pcm_dump
def build_token(self, appid: str, appcert: str) -> str:
return RealtimekitTokenBuilder.build_token(
appid, appcert, self.channel_name, self.uid
)
class AudioStream:
def __init__(self) -> None:
self.queue: asyncio.Queue = asyncio.Queue()
def __aiter__(self) -> AsyncIterator[PcmAudioFrame]:
return self
async def __anext__(self) -> PcmAudioFrame:
item = await self.queue.get()
if item is None:
raise StopAsyncIteration
return item
class ChannelEventObserver(
IRTCConnectionObserver, IRTCLocalUserObserver, IAudioFrameObserver
):
def __init__(self, event_emitter: AsyncIOEventEmitter, options: RtcOptions) -> None:
self.loop = asyncio.get_event_loop()
self.emitter = event_emitter
self.audio_streams = dict[int, AudioStream]()
self.options = options
def emit_event(self, event_name: str, *args):
"""Helper function to emit events."""
self.loop.call_soon_threadsafe(self.emitter.emit, event_name, *args)
def on_connected(
self, agora_rtc_conn: RTCConnection, conn_info: RTCConnInfo, reason
):
logger.info(f"Connected to RTC: {agora_rtc_conn} {conn_info} {reason}")
self.emit_event("connection_state_changed", agora_rtc_conn, conn_info, reason)
def on_disconnected(
self, agora_rtc_conn: RTCConnection, conn_info: RTCConnInfo, reason
):
logger.info(f"Disconnected from RTC: {agora_rtc_conn} {conn_info} {reason}")
self.emit_event("connection_state_changed", agora_rtc_conn, conn_info, reason)
def on_connecting(
self, agora_rtc_conn: RTCConnection, conn_info: RTCConnInfo, reason
):
logger.info(f"Connecting to RTC: {agora_rtc_conn} {conn_info} {reason}")
self.emit_event("connection_state_changed", agora_rtc_conn, conn_info, reason)
def on_connection_failure(self, agora_rtc_conn, conn_info, reason):
logger.error(f"Connection failure: {agora_rtc_conn} {conn_info} {reason}")
self.emit_event("connection_state_changed", agora_rtc_conn, conn_info, reason)
def on_user_joined(self, agora_rtc_conn: RTCConnection, user_id):
logger.info(f"User joined: {agora_rtc_conn} {user_id}")
self.emit_event("user_joined", agora_rtc_conn, user_id)
def on_user_left(self, agora_rtc_conn: RTCConnection, user_id, reason):
logger.info(f"User left: {agora_rtc_conn} {user_id} {reason}")
self.emit_event("user_left", agora_rtc_conn, user_id, reason)
def handle_received_chunk(self, json_chunk):
chunk = json.loads(json_chunk)
msg_id = chunk["msg_id"]
part_idx = chunk["part_idx"]
total_parts = chunk["total_parts"]
if msg_id not in self.received_chunks:
self.received_chunks[msg_id] = {"parts": {}, "total_parts": total_parts}
if (
part_idx not in self.received_chunks[msg_id]["parts"]
and 0 <= part_idx < total_parts
):
self.received_chunks[msg_id]["parts"][part_idx] = chunk
if len(self.received_chunks[msg_id]["parts"]) == total_parts:
# all parts received, now recomposing original message and get rid it from dict
sorted_parts = sorted(
self.received_chunks[msg_id]["parts"].values(),
key=lambda c: c["part_idx"],
)
full_message = "".join(part["content"] for part in sorted_parts)
del self.received_chunks[msg_id]
return full_message, msg_id
return (None, None)
def on_stream_message(
self, agora_local_user: LocalUser, user_id, stream_id, data, length
):
# logger.info(f"Stream message", agora_local_user, user_id, stream_id, length)
(reassembled_message, msg_id) = self.handle_received_chunk(data)
if reassembled_message is not None:
logger.info(f"Reassembled message: {msg_id} {reassembled_message}")
def on_audio_subscribe_state_changed(
self,
agora_local_user,
channel,
user_id,
old_state,
new_state,
elapse_since_last_state,
):
logger.info(
f"Audio subscribe state changed: {user_id} {new_state} {elapse_since_last_state}"
)
self.emit_event(
"audio_subscribe_state_changed",
agora_local_user,
channel,
user_id,
old_state,
new_state,
elapse_since_last_state,
)
def on_playback_audio_frame_before_mixing(
self, agora_local_user: LocalUser, channelId, uid, frame: AudioFrame
):
audio_frame = PcmAudioFrame()
audio_frame.samples_per_channel = frame.samples_per_channel
audio_frame.bytes_per_sample = frame.bytes_per_sample
audio_frame.number_of_channels = frame.channels
audio_frame.sample_rate = self.options.sample_rate
audio_frame.data = frame.buffer
# print(
# "on_playback_audio_frame_before_mixing",
# audio_frame.samples_per_channel,
# audio_frame.bytes_per_sample,
# audio_frame.number_of_channels,
# audio_frame.sample_rate,
# len(audio_frame.data),
# )
self.loop.call_soon_threadsafe(
self.audio_streams[uid].queue.put_nowait, audio_frame
)
return 0
class Channel:
def __init__(self, rtc: "RtcEngine", options: RtcOptions) -> None:
self.loop = asyncio.get_event_loop()
# Create the event emitter
self.emitter = AsyncIOEventEmitter(self.loop)
self.connection_state = 0
self.options = options
self.remote_users = dict[int, Any]()
self.rtc = rtc
self.chat = Chat(self)
self.channelId = options.channel_name
self.uid = options.uid
self.enable_pcm_dump = options.enable_pcm_dump
self.token = options.build_token(rtc.appid, rtc.appcert) if rtc.appcert else ""
conn_config = RTCConnConfig(
client_role_type=ClientRoleType.CLIENT_ROLE_BROADCASTER,
channel_profile=ChannelProfileType.CHANNEL_PROFILE_LIVE_BROADCASTING,
)
self.connection = self.rtc.agora_service.create_rtc_connection(conn_config)
self.channel_event_observer = ChannelEventObserver(
self.emitter,
options=options,
)
self.connection.register_observer(self.channel_event_observer)
self.local_user = self.connection.get_local_user()
self.local_user.set_playback_audio_frame_before_mixing_parameters(
options.channels, options.sample_rate
)
self.local_user.register_local_user_observer(self.channel_event_observer)
self.local_user.register_audio_frame_observer(self.channel_event_observer)
# self.local_user.subscribe_all_audio()
self.media_node_factory = self.rtc.agora_service.create_media_node_factory()
self.audio_pcm_data_sender = (
self.media_node_factory.create_audio_pcm_data_sender()
)
self.audio_track = self.rtc.agora_service.create_custom_audio_track_pcm(
self.audio_pcm_data_sender
)
self.audio_track.set_enabled(1)
self.local_user.publish_audio(self.audio_track)
self.stream_id = self.connection.create_data_stream(False, False)
self.received_chunks = {}
self.waiting_message = None
self.msg_id = ""
self.msg_index = ""
self.on(
"user_joined",
lambda agora_rtc_conn, user_id: self.remote_users.update({user_id: True}),
)
self.on(
"user_left",
lambda agora_rtc_conn, user_id, reason: self.remote_users.pop(
user_id, None
),
)
def handle_audio_subscribe_state_changed(
agora_local_user,
channel,
user_id,
old_state,
new_state,
elapse_since_last_state,
):
if new_state == 3: # Successfully subscribed
self.channel_event_observer.audio_streams.update(
{user_id: AudioStream()}
)
elif new_state == 0:
self.channel_event_observer.audio_streams.pop(user_id, None)
self.on("audio_subscribe_state_changed", handle_audio_subscribe_state_changed)
self.on(
"connection_state_changed",
lambda agora_rtc_conn, conn_info, reason: setattr(
self, "connection_state", conn_info.state
),
)
async def connect(self) -> None:
"""
Connects to a channel.
Parameters:
channelId: The channel ID.
uid: The user ID.
Returns:
Channel: The connected channel.
"""
if self.connection_state == 3:
return
future = asyncio.Future()
def callback(agora_rtc_conn: RTCConnection, conn_info: RTCConnInfo, reason):
logger.info(f"Connection state changed: {conn_info.state}")
if conn_info.state == 3: # Connection successful
future.set_result(None)
elif conn_info.state == 5: # Connection failed
future.set_exception(
Exception(f"Connection failed with state: {conn_info.state}")
)
self.on("connection_state_changed", callback)
logger.info(f"Connecting to channel {self.channelId} with token {self.token}")
self.connection.connect(self.token, self.channelId, f"{self.uid}")
if self.enable_pcm_dump:
agora_parameter = self.connection.get_agora_parameter()
agora_parameter.set_parameters("{"che.audio.frame_dump":{"location":"all","action":"start","max_size_bytes":"120000000","uuid":"123456789","duration":"1200000"}}")
try:
await future
except Exception as e:
raise Exception(
f"Failed to connect to channel {self.channelId}: {str(e)}"
) from e
finally:
self.off("connection_state_changed", callback)
async def disconnect(self) -> None:
"""
Disconnects the channel.
"""
if self.connection_state == 1:
return
disconnected_future = asyncio.Future[None]()
def callback(agora_rtc_conn: RTCConnection, conn_info: RTCConnInfo, reason):
self.off("connection_state_changed", callback)
if conn_info.state == 1:
disconnected_future.set_result(None)
self.on("connection_state_changed", callback)
self.connection.disconnect()
await disconnected_future
def get_audio_frames(self, uid: int) -> AudioStream:
"""
Returns the audio frames from the channel.
Returns:
AudioStream: The audio stream.
"""
return self.channel_event_observer.audio_streams[uid]
async def push_audio_frame(self, frame: bytes) -> None:
"""
Pushes an audio frame to the channel.
Parameters:
frame: The audio frame to push.
"""
audio_frame = PcmAudioFrame()
audio_frame.data = bytearray(frame)
audio_frame.timestamp = 0
audio_frame.bytes_per_sample = 2
audio_frame.number_of_channels = self.options.channels
audio_frame.sample_rate = self.options.sample_rate
audio_frame.samples_per_channel = int(
len(frame) / audio_frame.bytes_per_sample / audio_frame.number_of_channels
)
ret = self.audio_pcm_data_sender.send_audio_pcm_data(audio_frame)
logger.info(f"Pushed audio frame: {ret}, audio frame length: {len(frame)}")
if ret < 0:
raise Exception(f"Failed to send audio frame: {ret}, audio frame length: {len(frame)}")
async def clear_sender_audio_buffer(self) -> None:
"""
Clears the audio buffer which is used to send.
"""
self.audio_track.clear_sender_buffer()
async def subscribe_audio(self, uid: int) -> None:
"""
Subscribes to the audio of a user.
Parameters:
uid: The user ID to subscribe to.
"""
future = asyncio.Future()
def callback(
agora_local_user,
channel,
user_id,
old_state,
new_state,
elapse_since_last_state,
):
if new_state == 3: # Successfully subscribed
future.set_result(None)
# elif new_state == 1: # Subscription failed
# future.set_exception(
# Exception(
# f"Failed to subscribe {user_id} audio: state changed from {old_state} to {new_state}"
# )
# )
self.on("audio_subscribe_state_changed", callback)
self.local_user.subscribe_audio(uid)
try:
await future
except Exception as e:
raise Exception(
f"Audio subscription failed for user {uid}: {str(e)}"
) from e
finally:
self.off("audio_subscribe_state_changed", callback)
async def unsubscribe_audio(self, uid: int) -> None:
"""
Unsubscribes from the audio of a user.
Parameters:
uid: The user ID to unsubscribe from.
"""
future = asyncio.Future()
def callback(
agora_local_user,
channel,
user_id,
old_state,
new_state,
elapse_since_last_state,
):
if new_state == 3: # Successfully unsubscribed
future.set_result(None)
else: # Failed to unsubscribe
future.set_exception(
Exception(
f"Failed to unsubscribe {user_id} audio: state changed from {old_state} to {new_state}"
)
)
self.on("audio_subscribe_state_changed", callback)
self.local_user.unsubscribe_audio(uid)
try:
await future
except Exception as e:
raise Exception(
f"Audio unsubscription failed for user {uid}: {str(e)}"
) from e
finally:
self.off("audio_subscribe_state_changed", callback)
def _split_string_into_chunks(
self, long_string, msg_id, chunk_size=300
) -> list[dict[str:Any]]:
"""
Splits a long string into chunks of a given size.
Parameters:
long_string: The string to split.
msg_id: The message ID.
chunk_size: The size of each chunk.
Returns:
list[dict[str: Any]]: The list of chunks.
"""
total_parts = (len(long_string) + chunk_size - 1) // chunk_size
json_chunks = []
for idx in range(total_parts):
start = idx * chunk_size
end = min(start + chunk_size, len(long_string))
chunk = {
"msg_id": msg_id,
"part_idx": idx,
"total_parts": total_parts,
"content": long_string[start:end],
}
json_chunk = json.dumps(chunk, ensure_ascii=False)
json_chunks.append(json_chunk)
return json_chunks
async def send_stream_message(self, data: str, msg_id: str) -> None:
"""
Sends a stream message to the channel.
Parameters:
data: The data to send.
msg_id: The message ID.
"""
chunks = self._split_string_into_chunks(data, msg_id)
for chunk in chunks:
self.connection.send_stream_message(self.stream_id, chunk)
def on(self, event_name: str, callback):
"""
Allows external components to subscribe to events.
Parameters:
event_name: The name of the event to subscribe to.
callback: The callback to call when the event is emitted.
"""
self.emitter.on(event_name, callback)
def once(self, event_name: str, callback):
"""
Allows external components to subscribe to events once.
Parameters:
event_name: The name of the event to subscribe to.
callback: The callback to call when the event is emitted.
"""
self.emitter.once(event_name, callback)
def off(self, event_name: str, callback):
"""
Allows external components to unsubscribe from events.
Parameters:
event_name: The name of the event to unsubscribe from.
callback: The callback to remove from the event.
"""
self.emitter.remove_listener(event_name, callback)
class ChatMessage:
def __init__(self, message: str, msg_id: str) -> None:
self.message = message
self.msg_id = msg_id
class Chat:
def __init__(self, channel: Channel) -> None:
self.channel = channel
self.loop = self.channel.loop
self.queue = asyncio.Queue()
def log_exception(t: asyncio.Task[Any]) -> None:
if not t.cancelled() and t.exception():
logger.error(
"unhandled exception",
exc_info=t.exception(),
)
asyncio.create_task(self._process_message()).add_done_callback(log_exception)
async def send_message(self, item: ChatMessage) -> None:
"""
Sends a message to the channel.
Parameters:
item: The message to send.
"""
await self.queue.put(item)
# await self.queue.put_nowait(item)
async def _process_message(self) -> None:
"""
Processes messages in the queue.
"""
while True:
item: ChatMessage = await self.queue.get()
await self.channel.send_stream_message(item.message, item.msg_id)
self.queue.task_done()
# await asyncio.sleep(0)
class RtcEngine:
def __init__(self, appid: str, appcert: str):
self.appid = appid
self.appcert = appcert
if not appid:
raise Exception("App ID is required)")
config = AgoraServiceConfig()
config.audio_scenario = AudioScenarioType.AUDIO_SCENARIO_CHORUS
config.appid = appid
config.log_path = os.path.join(
os.path.dirname(
os.path.dirname(
os.path.dirname(os.path.join(os.path.abspath(__file__)))
)
),
"agorasdk.log",
)
self.agora_service = AgoraService()
self.agora_service.initialize(config)
def create_channel(self, options: RtcOptions) -> Channel:
"""
Creates a channel.
Parameters:
channelId: The channel ID.
uid: The user ID.
Returns:
Channel: The created channel.
"""
return Channel(self, options)
def destroy(self) -> None:
"""
Destroys the RTC engine.
"""
self.agora_service.release()
```
## Test the sample code [#test-the-sample-code-12]
Follow these steps to test the demo code:
1. Create a file named `rtc.py` and paste the [complete source code](#complete-code) into this file.
2. Create a file named `main.py` in the same folder as `rtc.py` and copy the following code to the file:
```python
import asyncio
from rtc import RtcEngine # Import the RtcEngine class from rtc.py
async def main():
appid = "" # Replace with your Agora App ID
channelId = "demo" # Replace with your desired channel ID
uid = "123" # Replace with your unique user ID
rtc_engine = RtcEngine(appid)
channel = await rtc_engine.connect(channelId, uid)
# Keep the script running to listen for events
await asyncio.Event().wait()
if __name__ == "__main__":
asyncio.run(main())
```
3. To specify the audio parameters, create a folder named `realtimeapi` and add a file `util.py` containing the following code:
```python
# Number of audio channels (1 for mono, 2 for stereo)
CHANNELS = 2
# Sample rate for audio processing (in Hz)
SAMPLE_RATE = 44100 # Common sample rates include 8000, 16000, 44100, 48000
```
4. To run the app, execute the following command in your terminal:
```bash
python3 main.py
```
You see output similar to the following:
```text
Initialization result: 0
LocalUserCB _on_audio_track_publish_start: 123456607304576 123456864576600
```
Congratulations! You have successfully connected to the Agora SDRTN® and joined a channel.
## Reference [#reference-12]
This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.
* If a firewall is deployed in your network environment, refer to [Connect with Cloud Proxy](build/optimize-quality-and-connection/cloud-proxy.mdx) to use Agora services normally.
### Next steps [#next-steps-12]
After implementing the quickstart sample, read the following documents to learn more:
* To ensure communication security in a test or production environment, best practice is to obtain and use a token from an authentication server. For details, see [Secure authentication with tokens](build/authenticate-users/use-tokens.mdx).
### API reference [#api-reference-11]
* [`rtc.py`](https://api-ref.agora.io/en/voice-sdk/python/rtc-py-api.html)
<_PlatformProcessedMarker close="true" />
# Agora skills (/en/realtime-media/broadcast-streaming/skills)
Agora skills is a set of structured reference files that give AI coding assistants deep knowledge of Agora's platform. When you ask your assistant to build something with Agora, it loads the relevant skill files covering products, APIs, and platform-specific code examples, so it can generate working code without guessing.
Skills includes integration with the [Agora MCP server](#agora-mcp-server), which gives your assistant access to live Agora documentation. For installation instructions and supported tools, see the [Agora Skills repository](https://github.com/AgoraIO/skills).
### Installation [#installation]
Install Agora Skills using one of the following methods:
#### Skills CLI (recommended) [#skills-cli-recommended]
Run the following command:
```bash
npx skills add github:AgoraIO/skills
```
Skills activate automatically when your agent detects relevant tasks, for example, "build a voice agent", "integrate Agora RTC", or "generate a token".
#### Manual installation [#manual-installation]
Clone the repository once and point your AI coding assistant to the skill files directly.
1. Clone the [Agora Skills repo](https://github.com/AgoraIO/skills.git):
```bash
git clone https://github.com/AgoraIO/skills.git ~/agora-skills
```
2. Point your AI assistant to `skills/agora/`.
Follow the instructions for your AI coding assistant:
Claude Code
Cursor
Windsurf
GitHub Copilot
Other tools
Run the following commands inside Claude Code:
* **User-level** (available across all your projects)
```bash
/plugin marketplace add AgoraIO/skills
/plugin install agora
```
* **Project-level** (shared with your team via version control)
```bash
/plugin marketplace add AgoraIO/skills
/plugin install agora --scope project
```
Copy or symlink `skills/agora/` into `.cursor/rules/`. For more information, see [Cursor skill directories](https://cursor.com/docs/skills#skill-directories).
Add `skills/agora/` to your Cascade context. For more information, see [Windsurf skills](https://docs.windsurf.com/windsurf/cascade/skills).
Reference via `@workspace` or add to `.github/copilot-instructions.md`. For more information, see [Create skills for Copilot in the CLI](https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/create-skills) or [Create skills for the Copilot coding agent](https://docs.github.com/en/copilot/how-tos/use-copilot-agents/coding-agent/create-skills).
The skill files are plain markdown. Point your tool to `skills/agora/` or load individual files directly. Use `SKILL.md` as the entry point.
# Subscription packages (/en/realtime-media/broadcast-streaming/subscription-packages)
A subscription package is a prepaid billing method. You can purchase a package in the [`Agora Console`](https://console.agora.io/subscriptions/rtc-plans?tab=monthly) to cover usage for the current month.
This page explains how to purchase, and manage subscription and top-up packages.
### Purchase and upgrade [#purchase-and-upgrade]
By default, a new account is assigned the Free package when the first project is created. You can upgrade at any time to the Starter, Pro, Business, or Business Plus. Higher-tier packages provide greater discounts and lower unit prices. To upgrade:
* Log in to [Agora Console](https://console.agora.io/subscriptions/rtc-plans?tab=monthly).
* In the sidebar, click **Subscriptions**.
* Under **All Subscriptions**, click **RTC Prepaid Plans**.
* Select your package and click **Subscribe**.

- For non-contracted customers, all packages, except the Enterprise package can be purchased directly from the Agora Console. To upgrade to the Enterprise package, contact [Agora sales](mailto\:sales@agora.io).
- You can upgrade sequentially or skip levels. For example, you may upgrade from Starter to Business Plus directly.
- Package upgrades take effect immediately and apply to the entire calendar month. If you make multiple upgrades on the same day, only the last upgrade is applied.
- If your account has a negative balance, purchasing a prepaid package first charges your credit card for the outstanding balance, then for the package fee.
#### Upgrade rules [#upgrade-rules]
When you upgrade to a paid package, it becomes effective for the entire calendar month, regardless of the purchase date. You receive the full monthly allocation of included minutes. The cost of the previous package is deducted from the new package price, so you only pay the difference. If you request an upgrade after auto-renewal has triggered, you may upgrade for the next month as well.
### Renewal [#renewal]
By default, your subscription package renews automatically 48 hours before it expires. Ensure your account has sufficient balance or enable auto-pay to avoid renewal failure. You can unsubscribe at any time to prevent renewal. See [Cancel subscription](#cancel-subscription).
If renewal fails:
* The system sends a notification and retries the renewal.
* If renewal still fails by the time the package expires, the package downgrades to the Free package.
* To restore the previous package, see [Purchase and Upgrade](#purchase-and-upgrade).
### Downgrade [#downgrade]
You can downgrade by selecting and purchasing a lower-tier package. A downgrade takes effect when your current package expires if you request it before auto-renewal is triggered. You are charged for the new package when you request the downgrade.
You can downgrade only once per month.
* If your downgrade for the next month is successful, auto-renewal for the current package is not triggered.
* If a package is already active, you must wait until the 1st day of that month to initiate a downgrade for the following month.
#### Example [#example]
* **Requested more than 48 hours before the current package expires**: The downgrade takes effect after the current package expires.
* **Requested less than 48 hours before the current package expires**: Since a package is already active for the next month, you must wait until the new package starts to downgrade for the following month.
### Cancel subscription [#cancel-subscription]
To cancel a prepaid package, downgrade to the Free package. Downgrade rules apply.
## Top-up package [#top-up-package]
A top-up package is a pay-as-you-go plan. You can purchase a top-up package from the [`Agora Console`](https://console.agora.io/subscriptions/rtc-plans?tab=top-up) to cover usage that exceeds your monthly package quota.
### Purchase [#purchase]
You can purchase multiple top-up packages. They are deducted in the order of purchase.
#### Example: [#example-1]
* Purchased 250,000-minutes top-up packages on **August 15** and **August 16**. On **September 1**, minutes consumed in August are deducted from the first package, then from the second.
### Validity [#validity]
Each top-up package is valid for one year, starting from the purchase date to the end of the same month of the following year.
#### Example: [#example-2]
* Purchased on **August 15, 2025** → valid until **August 31, 2026**
Unused minutes expire after the validity period and cannot be carried forward.
### Cancel top-up package [#cancel-top-up-package]
Cancelling a top-up package is not supported.
## Free tier overage [#free-tier-overage]
For Free tier users, exceeding the included minutes and any purchased top-up minutes results in service suspension. Service resumes when you purchase additional top-up minutes or upgrade to a prepaid subscription package.
# Core concepts (/en/realtime-media/cloud-recording/core-concepts)
Agora Cloud Recording enables you to record video and voice calls or streams in the cloud for storage or on-demand viewing. Cloud Recording works with Voice Calling, Video Calling, Broadcast Streaming and Interactive Live Streaming.
This page introduces the key processes and concepts you need to know to use Cloud Recording.
## General concepts [#general-concepts]
Agora relies on the following fundamental concepts to enable seamless real-time communication:
### Agora SDRTN® [#agora-sdrtn]
Agora's core engagement services are powered by its Software-Defined Real-Time Network (SDRTN®), a global infrastructure accessible anytime, anywhere. Unlike traditional networks, Agora SDRTN® is not restricted by devices, phone numbers, or telecom coverage areas. With data centers in over 200 countries and regions, it ensures sub-second latency and high availability for real-time media.
Agora SDRTN® enables live user engagement through real-time communication (RTC), offering:
* Unmatched quality of service
* High availability and accessibility
* True scalability
* Low cost
### App ID [#app-id]
The App ID is a unique key generated by Agora to identify each project and provide billing and other statistical data services. The App ID is critical for connecting users within your app. It is used to initialize the ENGINE in your app, and as one of the required keys to create authentication tokens for secure communication. Retrieve the App ID for your project using the [Agora Console](https://console.agora.io/v2/project-management).
App IDs are stored on the front-end client and do not provide access control. Projects using only an App ID allow any user with the App ID to join. For access control, especially in production environments, choose the **App ID + Token** mechanism for user authentication when creating a new project. Without authentication tokens, your environment is open to anyone with access to your App ID.
### App Certificate [#app-certificate]
An App Certificate is a unique key generated by the Agora Console to secure projects through token authentication. It is required, along with the App ID, to generate a token that proves authorization between your systems and Agora's network. App Certificates are used to generate Cloud Recording authentication tokens.
Store the App Certificate securely in your backend systems. If your App Certificate is compromised or to meet security compliance requirements, you can invalidate certificates and create new ones through the Agora Console.
### Tokens [#tokens]
A token is a dynamic key generated using the App ID, App Certificate, user ID, and expiration timestamp. Tokens authenticate and secure access to Agora's services, ensuring only authorized users can join a channel and participate in real-time communication.
Tokens are generated on your server and passed to the client for use in Cloud Recording. The token generation process involves digitally signing the App ID, App Certificate, user ID, and expiration timestamp using a specific algorithm, preventing tampering or forgery.
During development and testing, use the Agora Console to generate temporary tokens. For production environments, implement a token server as part of your security infrastructure to control access to your channels.
For information on setting up a token server for generating and managing tokens, refer to [Deploy a token server](/en/realtime-media/cloud-recording/build/set-up-authentication/authentication-workflow).
### Channel [#channel]
A channel organizes users into a group and is identified by a unique channel name. Users who connect to the same channel are able to communicate with each other. A channel is created when the first user joins and ceases to exist when the last user leaves.
Channels are created by calling the methods for transmitting real-time data. Agora uses different channels to transmit different types of data:
* A Video SDK channel is used for transmitting audio or video data.
* A MESS channel is used for transmitting messaging or signaling data.
These channels are independent of each other.
Additional services provided by Agora, such as Cloud Recording and STT, join the SDK channel to provide real-time recording, transmission acceleration, media playback, and content moderation.
### User ID [#user-id]
In Cloud Recording, the UID is an integer value that uniquely identifies a user within the context of a channel. When joining a channel, you have the option to either assign a specific UID to the user or pass `0` or `null` and allow Agora to automatically generate and assign a UID to the user. If two users attempt to join the same channel with the same UID, it can lead to unexpected behavior.
The UID is used by Agora's services and components to identify and manage users within a channel. Ensure that UIDs are properly assigned to prevent conflicts.
### Agora Console [#agora-console]
To use Agora Cloud Recording, create a project in the [Agora Console](https://console.agora.io/v2) first.

Agora Console provides an intuitive interface for developers to query and manage their Agora account. After registering an Agora account, you use the Agora Console to perform the following tasks:
* Manage your account
* Create and configure Agora projects and services
* Get an App ID and the App certificate
* Generate temporary tokens for development and testing
* Manage members and roles
* Check call quality and usage
* Check bills and make payments
* Access product resources
See [Agora account management](/en/realtime-media/cloud-recording/manage-agora-account) for details on how to manage all aspects of your Agora account.
Agora also provides RESTful APIs that you use to implement features such as creating a project and fetching usage numbers programmatically.
## Cloud recording concepts [#cloud-recording-concepts]
### Recording modes [#recording-modes]
Agora Cloud Recording supports three recording modes:
* Individual recording
* Composite recording
* Web page recording
After the recording is complete, the recorded content is uploaded as a `TS` file to the third-party cloud storage you specified. An `M3U8` file is also generated to serve as an index for the corresponding `TS` file
The working principles of different recording modes and the types of files generated by Cloud Recording are as follows:
#### Individual recording [#individual-recording]
In individual recording, the recording service records the audio and video streams of each UID in the channel separately. After the recording is complete, the recording service generates the corresponding audio and video files for each UID.
For example, if there are 3 UIDs in the channel and each UID sends audio and video, then in the individual recording mode, 3 audio files and 3 video files are generated.
#### Composite recording [#composite-recording]
In mixed recording, the recording service combines the audio and video of multiple UIDs in the channel into a single audio and video file.
For example, if there are 3 UIDs in the channel and each sends audio and video, the mixed recording mode generates one recording file that includes the audio and video of all UIDs.
#### Web page recording [#web-page-recording]
In web page recording, the recording service combines the page content and audio of a specified web page into an audio and video file.
Web page recording is commonly used in the following use-cases:
* In online classrooms, to record the teacher and student audio and video along with courseware, whiteboard, and other visuals.
* In video conferences, to capture participants' audio and video, as well as whiteboard, PPT, and other visuals.
### Transcoding and non-transcoding modes [#transcoding-and-non-transcoding-modes]
In individual recording, audio transcoding and non-transcoding modes have different use cases and characteristics.
**Individual recording with transcoding**: This mode is used in use-cases where unified audio encoding parameters are needed to ensure consistent recording file formats and parameters for easier post-processing and playback. It is commonly used in cases requiring high compatibility and standardized output, such as wide player support and standardized storage.
**Individual recording without transcoding**: This mode is used when the original audio encoding parameters must be preserved to maintain the sound quality and performance. It is often used in use-cases with high demands for real-time performance and original sound quality, such as high-fidelity audio recording.
| Feature | Individual recording with transcoding | Individual recording without transcoding |
| :--------------------------------- | :------------------------------------------------------------------------------------------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Transcoding during audio encoding | Yes | No |
| Raw audio data | The sampling rate, number of channels, and bitrate are fixed at 48 kHz, mono, and 48 Kbps respectively. | The bitrate, sampling rate and number of channels are determined by the audio encoding parameters of the streaming end `AudioProfile`. |
| Audio encoding format | LC-AAC | Determined by the configuration of the source end `AudioProfile` |
| Generated recording files | Each UID generates an audio file in `M3U8` format and multiple audio files in `TS` format. | Same as transcoding recording. If the user stops streaming using `muteLocalAudioStream` or `leaveChannel` audio recording stops immediately, and there is no 15 seconds of silent data. |
| Player compatibility | The recorded file can be played by any mainstream player that supports the HLS protocol. | The audio encoding format is determined by the configuration of the streaming end `AudioProfile`. Different audio encoding formats have different compatibility. |
### Delayed transcoding [#delayed-transcoding]
Delayed transcoding is designed for audio-only recording use-cases. When you enable this mode, the recording service merges and transcodes the audio files of all users in the specified channel into an `MP3`, `M4A`, or `AAC` file within 24 hours after the recording ends (or up to 48 hours in special cases) and uploads it to the specified third-party cloud storage.
### Delayed audio mixing [#delayed-audio-mixing]
Delayed audio mixing is used for individual audio recording use-cases. To obtain a mixed recording file of all users in the channel after recording, you enable the delayed audio mixing feature when starting individual audio recording without transcoding. Once enabled, the recording service merges and transcodes the audio files of all users in the specified channel into an `MP3`, `M4A`, or `AAC` file within 24 hours after the recording is complete (or up to 48 hours in special cases) and uploads it to the specified third-party cloud storage.
### Slicing [#slicing]
Slicing involves cutting audio and video data according to specific rules during the recording process to generate multiple recording files. After slicing, several slice files (such as `TS` or `WebM` files) are created, along with `M3U8` files that store the indexes of these slice files.
# Cloud Recording overview (/en/realtime-media/cloud-recording)
Agora's Cloud Recording is a RESTful API-based solution for recording real-time voice, video, and interactive streaming sessions directly to your cloud storage. It supports leading CDN and cloud hosting providers, offers customizable recording layouts, and supports multiple file formats for playback and distribution.
Easily launch multiple recorders within a single channel to ensure redundancy or to capture different layouts and formats simultaneously. This makes Cloud Recording a flexible and reliable solution for recording and preserving real-time communication.
## Features [#features]
Supports globally distributed cluster deployment and automatically backs up files on Agora’s cloud server when the third-party cloud storage fails.
End-to-end security mechanisms for voice and video calls, data transmission, and storage.
Supports third-party cloud storage, including Amazon S3, Alibaba Cloud, Tencent Cloud, Kingsoft Cloud, Qiniu Cloud, Microsoft Azure, Google Cloud, Huawei Cloud, and Baidu AI Cloud.
Simple implementation and easy to learn with RESTful API calls that allow you to start, stop, and query the recording.
# Agora account management (/en/realtime-media/cloud-recording/manage-agora-account)
This page shows you how to sign up for an Agora account, create a new project, and get the app ID and app certificate to generate a temporary token.
## Get started with Agora [#get-started-with-agora]
To join a Cloud Recording session, you need an Agora App ID. This section shows you how to set up an Agora account, create an Agora project and get the required information from [Agora Console](https://console.agora.io/v2).
### Sign up for an Agora account [#sign-up-for-an-agora-account]
To use Agora products and services, create an Agora account with your email, phone number, or a third-party account.
Sign up with email
Sign up with a third-party account
1. Go to the [signup page](https://sso.agora.io/en/signup).
2. Fill in the required fields.
3. Carefully read the **Terms of Service**, **Privacy Policy**, and **Acceptable Use Policy**, and tick the checkbox.
4. Click **Continue**.
5. Enter your **verification code** and click **Confirm**.
6. Follow the on-screen instructions to provide your name, company name, and phone number, set a password, and click **Continue**.
1. On the [Agora Console](https://console.agora.io/v2) login page, select the third-party account you want to use.
2. Follow the on-screen instructions to complete verification.
3. Click **Create a new account**.
4. Carefully read the **Terms of Service**, **Privacy Policy**, and **Acceptable Use Policy**, and select the checkbox.
5. Click **Continue**.
Once you sign up successfully, your account is automatically logged in. Follow the on-screen instructions to create your first project and test out real-time communications.
For later visits, log in to [Agora Console](https://console.agora.io/v2) with your phone number, email address, or linked third-party account.
### Create an Agora project [#create-an-agora-project]
To create an Agora project, do the following:
1. In [Agora Console](https://console.agora.io/v2), open the [Projects](https://console.agora.io/v2/project-management) page.
2. Click **Create New**.
3. Follow the on-screen instructions to enter a project name and use case, and check **Secured mode: APP ID + Token (Recommended)** as the authentication mechanism.

4. Click **Submit**. You see the new project on the **Projects** page.
### Get the App ID [#get-the-app-id]
Agora automatically assigns a unique identifier to each project, called an App ID.
To copy this App ID, find your project on the [Projects](https://console.agora.io/v2/project-management) page in Agora Console, and click the copy icon in the **App ID** column.

## Security and authentication [#security-and-authentication]
Use the following features from your Agora account to implement security and authentication features in your apps.
### Get the App Certificate [#get-the-app-certificate]
When generating an authentication token on your app server, you need an App Certificate, in addition to the App ID.
To get an App Certificate, do the following:
1. On the [Projects](https://console.agora.io/v2/project-management) page, click the pencil icon to edit the project you want to use.

2. Click the copy icon under **Primary Certificate**.

### Generate temporary tokens [#generate-temporary-tokens]
To ensure communication security, best practice is to use tokens to authenticate the users who log in from your app.
To generate a temporary RTC token for use in your Video SDK projects:
1. On the [Projects](https://console.agora.io/v2/project-management) page, click the pencil icon next to your project.
2. On the **Security** panel, click **Generate Temp Token**, enter a channel name in the pop-up box and click **Generate**. Copy the generated RTC token for use in your Cloud Recording projects.
To generate a token for other Agora products:
1. In your browser, navigate to the [Agora token builder](https://agora-token-generator-demo.vercel.app/).
2. Choose the Agora product your user wants to log in to. Fill in **App ID** and **App Certificate** with the
details of your project in Agora Console.
3. Customize the token for each user. The required fields are visible in the Agora token builder.
4. Click **Generate Token**.
The token appears in Token Builder.
5. Copy the token and use it in your app.
For more information on managing other aspects of your Agora account, see [Agora console overview](/en/realtime-media/video/reference/console-overview).
# Agora MCP (/en/realtime-media/cloud-recording/mcp)
The Agora MCP server gives your AI assistant direct access to Agora's documentation, so it can look up APIs, SDK methods, and platform-specific details in real time.
The Agora MCP server is included when you install Agora Skills. If you prefer to install only the MCP server, it is available at:
```text
https://mcp.agora.io
```
## Installation [#installation]
Refer to the installation instructions for your coding assistant.
Cursor
Claude
Codex
Gemini CLI
Manual installation
Click the button below to install the MCP server in [Cursor](https://www.cursor.com/)
or add it manually with the following JSON:
```json
{
"mcpServers": {
"agora-docs": {
"url": "https://mcp.agora.io"
}
}
}
```
* **Claude Code**
Run the following command in your terminal to install the MCP server in [Claude Code](https://claude.com/product/claude-code):
```bash
claude mcp add --transport http agora-docs https://mcp.agora.io
```
* **Claude Desktop**
In **Settings**, select **Connectors** and then choose **Add custom connector**. Enter the following values and click **Add**:
* **Name**: `agora-docs`
* **Remote MCP server URL**: `https://mcp.agora.io`
Run the following command in your terminal to install the server in [OpenAI Codex](https://openai.com/codex/):
```bash
codex mcp add --url https://mcp.agora.io agora-docs
```
Run the following command in your terminal to install the server in [Gemini CLI](https://github.com/google-gemini/gemini-cli):
```bash
gemini mcp add --transport http agora-docs https://mcp.agora.io
```
Add the server URL `https://mcp.agora.io` to your MCP client of choice. If prompted, set the transport to `http` or "Streamable HTTP".
## Getting started [#getting-started]
Once installed, your coding assistant has access to Agora's documentation through the MCP server. The assistant will intelligently use this resource when relevant to your questions. For more targeted results, mention Agora along with your target product and platform, such as `Cloud Recording`, `Web`, and `REST API` in your prompts.
## AGENTS.md [#agentsmd]
To get the most out of Agora Skills and the MCP server, Agora recommends that you include an [AGENTS.md](https://agents.md) or similar file in your repository. This file ensures that your agent always consults Agora docs to produce reliable, working code.
Add the following instructions to your own `AGENTS.md` file:
```markdown
## Agora Documentation
Agora documentation is updated frequently. You should always refer to the latest documentation when working with Agora products. Agora offers an MCP server that you can use to browse and search its documentation. If the developer has not yet installed this server, recommend that they install it at https://mcp.agora.io.
If the Agora Skills files are available in your context, use them for platform-specific API guidance and code examples.
```
# Quickstart using middleware (/en/realtime-media/cloud-recording/middleware-quickstart)
To streamline the use of Agora RESTful APIs within your infrastructure, Agora’s developer community offers the open-source [Agora Go Backend Middleware](https://github.com/AgoraIO-Community/agora-go-backend-middleware). This backend provides RESTful APIs for tasks such as token generation, cloud recording management, and real-time transcription. It simplifies the integration of Agora’s cloud services into your real-time voice and video applications. This guide shows you how to implement cloud recording using the community middleware.
## Understand the tech [#understand-the-tech]
The following figure illustrates the architecture of the middleware cloud recording micro service.

## Set up and run the Go backend middleware [#set-up-and-run-the-go-backend-middleware]
Take the following steps to set up and run the middleware project:
1. **Clone the repository**
```bash
git clone https://github.com/AgoraIO-Community/agora-go-backend-middleware.git
```
2. **Install dependencies**
Ensure you have [Go](https://go.dev/doc/install) installed on your system. Navigate to the project directory and install the project dependencies:
```bash
cd agora-go-backend-middleware
go mod download
```
3. **Configure environment variables**
1. Copy the example `.env` file.
```bash
cp .env.example .env
```
2. Update the following variables in the `.env` file:
* `APP_ID`: Your Agora App ID.
* `APP_CERTIFICATE`: Your Agora App Certificate.
* `CUSTOMER_ID`: Your customer ID
* `CUSTOMER_SECRET`: Your Customer Secret
* `STORAGE_VENDOR`: Cloud storage vendor (e.g., AWS, GCP).
* `STORAGE_REGION`: Region of your cloud storage.
* `STORAGE_BUCKET`: Bucket name.
* `STORAGE_ACCESS_KEY`: Cloud storage access key.
* `STORAGE_SECRET_KEY`: Cloud storage secret key.
4. **Run the middleware:**
Start the middleware server using the following command:
```bash
go run cmd/main.go
```
The middleware runs on the default port, for example `localhost:8080`.
## Implement Cloud Recording using middleware [#implement-cloud-recording-using-middleware]
This section explains the RESTful API calls to the backend middleware for starting, managing, and stopping a Cloud Recording session.
### Start recording [#start-recording]
To start a Cloud Recording session refer to the following examples:
The command-line examples in this guide are for demonstration purposes only. Do not use them directly in a production environment. Implement RESTful API requests through your application server.
* Basic example
```bash
curl -X POST http://localhost:8080/cloud_recording/start \
-H "Content-Type: application/json" \
-d '{
"channelName": "test_channel",
"sceneMode": "realtime",
"recordingMode": "mix",
"excludeResourceIds": []
}'
```
* Advanced example
```bash
curl -X POST http://localhost:8080/cloud_recording/start \
-H "Content-Type: application/json" \
-d '{
"channelName": "testChannel",
"sceneMode": "realtime",
"recordingMode": "mix",
"excludeResourceIds": [],
"recordingConfig": {
"channelType": 0,
"decryptionMode": 1,
"secret": "your_secret",
"salt": "your_salt",
"maxIdleTime": 120,
"streamTypes": 2,
"videoStreamType": 0,
"subscribeAudioUids": ["#allstream#"],
"unsubscribeAudioUids": [],
"subscribeVideoUids": ["#allstream#"],
"unsubscribeVideoUids": [],
"subscribeUidGroup": 0,
"streamMode": "individual",
"audioProfile": 1,
"transcodingConfig": {
"width": 640,
"height": 360,
"fps": 15,
"bitrate": 500,
"maxResolutionUid": "1",
"layoutConfig": [
{
"x_axis": 0,
"y_axis": 0,
"width": 640,
"height": 360,
"alpha": 1,
"render_mode": 1
}
]
}
}
}'
```
### Stop recording [#stop-recording]
To stop an ongoing cloud recording session:
```bash
curl -X POST http://localhost:8080/cloud_recording/stop \
-H "Content-Type: application/json" \
-d '{
"cname": "test_channel",
"uid": "uid-from-start-response",
"resourceId": "resource-id-from-start-response",
"sid": "sid-from-start-response",
"recordingMode": "mix",
"async_stop": false
}'
```
### Get Recording Status [#get-recording-status]
During the recording, call the `status` endpoint to query the recording status as required.
```bash
curl -X GET "http://localhost:8080/cloud_recording/status?resourceId=your-resource-id&sid=your-sid&mode=mix"
```
### Update Subscriber List [#update-subscriber-list]
To update the subscriber list during a recording session, refer to the following example:
```bash
curl -X POST http://localhost:8080/cloud_recording/update/subscriber-list \
-H "Content-Type: application/json" \
-d '{
"cname": "test_channel",
"uid": "uid-from-start-response",
"resourceId": "your-resource-id",
"sid": "your-sid",
"recordingMode": "mix",
"recordingConfig": {
"streamSubscribe": {
"audioUidList": {
"subscribeAudioUids": ["2345", "3456"]
},
"videoUidList": {
"subscribeVideoUids": ["2345", "3456"]
}
}
}
}'
```
### Update Layout [#update-layout]
To update the layout of a recording session, refer to the following example:
```bash
curl -X POST http://localhost:8080/cloud_recording/update/layout \
-H "Content-Type: application/json" \
-d '{
"cname": "test_channel",
"uid": "uid-from-start-response",
"resourceId": "your-resource-id",
"sid": "your-sid",
"recordingMode": "mix",
"recordingConfig": {
"mixedVideoLayout": 1,
"backgroundColor": "#000000",
"layoutConfig": [
{
"uid": "2345",
"x_axis": 0,
"y_axis": 0,
"width": 360,
"height": 640,
"alpha": 1,
"render_mode": 1
}
]
}
}'
```
## Cloud Recording Middleware API Reference [#cloud-recording-middleware-api-reference]
This section provides details about the Go middleware Cloud Recording API endpoints.
### Start Recording [#start-recording-1]
Starts a cloud recording session.
##### Endpoint [#endpoint]
**POST:** `/cloud_recording/start`
#### Request Body [#request-body]
```json
{
"channelName": "string",
"uid": "string",
"recordingConfig": {
// RecordingConfig fields
},
"storageConfig": {
// StorageConfig fields
}
}
```
#### Response [#response]
```json
{
"resourceId": "string",
"sid": "string",
"timestamp": "string"
}
```
### Stop Recording [#stop-recording-1]
Stops an ongoing cloud recording session.
#### Endpoint [#endpoint-1]
**POST:** `/cloud_recording/stop`
#### Request Body [#request-body-1]
```json
{
"cname": "string",
"uid": "string",
"resourceId": "string",
"sid": "string",
"recordingMode": "string",
"async_stop": boolean
}
```
#### Response [#response-1]
```json
{
"resourceId": "string",
"sid": "string",
"serverResponse": {
"fileListMode": "string",
"fileList": [
{
"fileName": "string",
"trackType": "string",
"uid": "string",
"mixedAllUser": boolean,
"isPlayable": boolean,
"sliceStartTime": number
}
]
},
"timestamp": "string"
}
```
### Get Recording Status [#get-recording-status-1]
Retrieves the status of a cloud recording session.
#### Endpoint [#endpoint-2]
**GET:** `/cloud_recording/status`
#### Query Parameters [#query-parameters]
* `resourceId`: string
* `sid`: string
* `mode`: string
#### Response [#response-2]
```json
{
"resourceId": "string",
"sid": "string",
"serverResponse": {
"fileListMode": "string",
"fileList": [
{
"fileName": "string",
"trackType": "string",
"uid": "string",
"mixedAllUser": boolean,
"isPlayable": boolean,
"sliceStartTime": number
}
]
},
"timestamp": "string"
}
```
### Update Subscriber List [#update-subscriber-list-1]
Updates the subscriber list for a cloud recording session.
#### Endpoint [#endpoint-3]
**POST:** `/cloud_recording/update/subscriber-list`
#### Request Body [#request-body-2]
```json
{
"cname": "string",
"uid": "string",
"resourceId": "string",
"sid": "string",
"recordingMode": "string",
"recordingConfig": {
// UpdateSubscriptionClientRequest fields
}
}
```
#### Response [#response-3]
```json
{
"cname": "string",
"uid": "string",
"resourceId": "string",
"sid": "string",
"timestamp": "string"
}
```
### Update Layout [#update-layout-1]
Updates the layout of a cloud recording session.
#### Endpoint [#endpoint-4]
**POST:** `/cloud_recording/update/layout`
#### Request Body [#request-body-3]
```json
{
"cname": "string",
"uid": "string",
"resourceId": "string",
"sid": "string",
"recordingMode": "string",
"recordingConfig": {
// UpdateLayoutClientRequest fields
}
}
```
#### Response [#response-4]
```json
{
"cname": "string",
"uid": "string",
"resourceId": "string",
"sid": "string",
"timestamp": "string"
}
```
Replace `localhost:8080` with your server's address, if it is different.
# REST quickstart (/en/realtime-media/cloud-recording/rest-quickstart)
Cloud Recording enables you to record and store real-time audio and video streams from channels. To implement Cloud Recording, you set up a self-hosted backend that interacts with Agora servers to manage recording tasks. This guide introduces the essential Cloud Recording RESTful APIs you use to manage the recording process.
The command-line examples in this guide are for demonstration purposes only. Do not use them directly in a production environment. Implement RESTful API requests through your application server or use Agora's [Go backend middleware](https://github.com/AgoraIO-Community/agora-go-backend-middleware). For details, see [Quickstart using middleware](/en/realtime-media/cloud-recording/middleware-quickstart)
## Understand the tech [#understand-the-tech]
The basic process of implementing Cloud Recording is as follows:

1. Get a resource ID
Before starting a cloud recording, call the [`acquire`](#acquire) method to obtain a cloud recording resource ID. After calling this method successfully, you get a resource ID in the response body.
2. Start cloud recording
Call the [`start`](#start) method to join the channel and start a cloud recording. After calling this method successfully, you get a recording ID from the response body to identify the current recording process.
3. Query the recording status
Call the [`query`](#query) method to check the recording status during the recording.
4. Stop cloud recording
Call the [`stop`](#stop) method to stop the cloud recording.
5. Upload the recording file
After the recording ends, the cloud recording service uploads the recording file to the [third-party cloud storage](/en/api-reference/api-ref/cloud-recording#storageconfig) you specify.
## Prerequisites [#prerequisites]
To implement Cloud Recording, ensure that you have:
* A valid Agora account and project. Obtain the following parameters from [Agora Console](https://console.agora.io/v2):
* The App ID and App certificate for your project. See [Get Started with Agora](../get-started/manage-agora-account#get-the-app-id)
* A valid temporary token
* Customer ID and Customer key for RESTful API. See [Authenticate REST calls](../reference/restful-authentication)
* Set up and enabled a supported third-party cloud storage service. Obtain the following parameters for your storage:
* Bucket name
* Access key
* Secret key
- [Alibaba Cloud](https://www.alibabacloud.com/product/object-storage-service)
- [Amazon S3](https://aws.amazon.com/s3/?nc1=h_ls)
- [Baidu AI Cloud](https://intl.cloud.baidu.com/product/bos.html)
- [Google Cloud](https://cloud.google.com/storage)
- [Huawei Cloud](https://www.huaweicloud.com/intl/en-us/product/obs)
- [Kingsoft Cloud](https://en.ksyun.com/nv/product/KS3)
- [Microsoft Azure](https://azure.microsoft.com/en-us/services/storage/blobs/)
- [Qiniu Cloud](https://www.qiniu.com/en/products/kodo)
- [Tencent Cloud](https://intl.cloud.tencent.com/product/cos)
* Enabled the Agora Cloud Recording service for your project.
1. Log in to [Agora Console](https://console.agora.io/v2), and click the **Project Management** icon on the left navigation panel.
2. On the **Project Management** page, find the project for which you want to enable the cloud recording service, and click the edit icon.
3. On the **Edit Project** page, find **Cloud Recording**, and click **Enable**.

On the following screen, click **Enable Cloud Recording**.

4. Click **Apply**.
Now, you can use Agora Cloud Recording and see the usage statistics in the **Usage** page.
* If your network has a firewall, follow the instructions in [Firewall Requirements](/en/realtime-media/cloud-recording/reference/firewall).
## Implement cloud recording [#implement-cloud-recording]
The following figure shows the API call sequence to start and manage cloud recording:

The following APIs are optional and can be called multiple times. However, they must be called during a recording session, that is, after recording starts and before it ends:
* [`query`](#query): Query the recording status
* [`update`](/en/api-reference/api-ref/cloud-recording#update): Update the subscription list
* [`updateLayout`](/en/api-reference/api-ref/cloud-recording#updatelayout): Update the video layout
### Use basic HTTP authentication [#use-basic-http-authentication]
The Cloud Recording RESTful APIs require basic HTTP authentication. You need to set the `Authorization` parameter in every HTTP request header. For details, see [Basic HTTP authentication](../reference/restful-authentication).
### Get a resource ID [#acquire]
Call the [`acquire`](/en/api-reference/api-ref/cloud-recording#acquire) method to request a resource ID for Cloud Recording.
After calling this method successfully, you receive a resource ID in the response body. The resource ID is valid for five minutes. Start recording with this resource ID within the validity period. One resource ID can only be used for a single recording session.
* The recording service is equivalent to a non-streaming client in the channel. The `uid` parameter in the request body is used to identify the recording service and cannot be the same as any existing user ID in the channel. For example, if there are two users already in the channel and their user IDs are 123 and 456, the `uid` cannot be `"123"` or `"456"`.
* Agora Cloud Recording does not support user IDs in string format (User Accounts). Ensure that every user in the channel has an integer user ID. The string content of the `uid` parameter must also be an integer.
#### Sample code [#sample-code]
For testing purposes, use the following command in the terminal to call the `acquire` method.
```bash
# Replace with the App ID of your Agora project
curl --location --request POST 'https://api.agora.io/v1/apps//cloud_recording/acquire' \
# Replace with the Base64-encoded credential in basic HTTP authentication
--header 'Authorization: Basic ' \
--header 'Content-Type: application/json' \
--data-raw '{
# Replace with the name of the channel you need to record
"cname": "",
# Replace with your user ID
"uid": "",
"clientRequest":{}
}'
```
### Start recording [#start]
Call the [`start`](/en/api-reference/api-ref/cloud-recording#start) method within five minutes of getting a resource ID to join a channel and start recording. You can choose either [individual recording](/en/realtime-media/cloud-recording/build/start-a-recording/individual-mode) or [composite recording](/en/realtime-media/cloud-recording/build/start-a-recording/composite-mode) as the recording mode.
If this method call succeeds, you receive a recording ID (sid) in the HTTP response body. This ID identifies the current recording.
After you obtain the recording ID `sid`, you can call the `query`, `updateLayout`, and `stop` methods before the time set (in hours) by `resourceExpiredHour` has passed.
#### Sample code [#sample-code-1]
For testing purposes, use the following command in the terminal to call the `start` method.
```bash
# Replace with the App ID of your Agora project
# Replace with the resource ID obtained through the acquire method
# Replace "" with "individual" for individual recording or "composite" for composite recording
curl --location --request POST 'https://api.agora.io/v1/apps//cloud_recording/resourceid//mode//start' \
# Replace with the Base64-encoded credential in basic HTTP authentication
--header 'Authorization: Basic ' \
--header 'Content-Type: application/json' \
--data-raw '{
# Replace with the name of the channel you need to record.
"cname": "",
# Replace with your user ID that identifies the recording service.
"uid": "",
"clientRequest": {
# Replace with the temporary token you obtain from the console.
"token": "",
# Set the storageConfig related parameters.
"storageConfig": {
"secretKey": "",
"vendor": 0,
"region": 0,
"bucket": "",
"accessKey": ""
},
# Set the recordingConfig related parameters.
"recordingConfig": {
# Which is consistent with the "channelType" of the Agora Video SDK.
"channelType": 0
}
}
}'
```
### Query recording status [#query]
During a recording session, can call the [`query`](/en/api-reference/api-ref/cloud-recording#query) method to query the recording status. You can call this API multiple times.
When you call this method successfully, you receive the current recording status and related information about the recording file in the response body. See [Best Practices in Integrating Cloud Recording](/en/realtime-media/cloud-recording/build/best-practices/integration-best-practices) for details about how to [Monitor service status during a recording](../best-practices/integration-best-practices#monitor-service-status-during-a-recording) and[ Obtain the M3U8 file name](../best-practices/integration-best-practices#obtain-the-m3u8-file-name).
#### Sample code [#sample-code-2]
For testing purposes, use the following command in the terminal to call the `query` method.
```bash
# Replace with the App ID of your Agora project
# Replace with the resource ID obtained through the acquire method
# Replace with the sid obtained through the start method
# Replace "" with "individual" for individual recording or "composite" for composite recording
curl --location --request GET 'https://api.agora.io/v1/apps//cloud_recording/resourceid//sid//mode//query' \
# Replace with the Base64-encoded credential in basic HTTP authentication
--header 'Authorization: Basic ' \
--header 'Content-Type: application/json'
```
### Stop recording [#stop]
Call the [`stop`](/en/api-reference/api-ref/cloud-recording#stop) API to end the recording session.
When you call this method successfully, you receive the status of the recording file upload and information about the recording file in the response body.
#### Sample code [#sample-code-3]
For testing purposes, use the following command in the terminal to call the `stop` method.
```bash
# Replace with the App ID of your Agora project
# Replace with the resource ID obtained through the acquire method
# Replace with the sid obtained through the start method
# Replace "" with "individual" for individual recording or "composite" for composite recording
curl --location --request POST
'https://api.agora.io/v1/apps//cloud_recording/resourceid//sid//mode//stop' \
--header 'Content-Type: application/json;charset=utf-8' \
# Replace with the Base64-encoded credential in basic HTTP authentication
--header 'Authorization: Basic ' \
--data-raw '{
# Replace with your user ID that identifies the recording service.
"uid": "",
# Replace with the name of the channel you are recording.
"cname": "",
"clientRequest":{
}
}'
```
Parameter settings
* If the `uid` parameter in the request body is the same as a user ID in the channel, or if you use a non-integer user ID, the recording fails. For details, see the notes on the `uid` parameter in the section [Get a cloud recording resource](#acquire).
* When the `start` request returns `200`, it only means that the RESTful API request is successful. To ensure that the recording has started successfully and continues normally, call `query` to check the recording status. Errors such as unreasonable `transcodingConfig` parameter settings, incorrect third-party cloud storage information, or incorrect token information cause the `query` method to return `404`. See [Why do I get a 404 error when I call query after successfully starting a cloud recording? ](../reference/common-errors#errors).
* Set the `maxIdleTime` parameter based on your business needs. Within the time range set by `maxIdleTime`, the recording continues and billing is generated even if the channel is idle.
## Reference [#reference]
This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.
### Sample project [#sample-project]
Agora provides a [Postman collection](https://documenter.getpostman.com/view/6319646/SVSLr9AM), which contains sample requests of RESTful API for a cloud recording. You can use the collection to quickly grasp the basic functionalities of the Cloud Recording RESTful APIs. You only need to import the collection to Postman and set your environment variables.
You can also use Postman to generate code snippets written in various programming languages. To do so, select a request, click **Code**, and select the desired language in **GENERATE CODE SNIPPETS**.

### See also [#see-also]
* To streamline the use of Agora RESTful APIs within your infrastructure, see
[Quickstart using middleware](/en/realtime-media/cloud-recording/middleware-quickstart). The community middleware project provides RESTful APIs for tasks such as token generation and cloud recording management.
* To update the subscription lists during the recording, call [`update`](/en/api-reference/api-ref/cloud-recording#update). You can call this method multiple times. See [Set up subscription lists](/en/realtime-media/cloud-recording/build/customize-the-recording/subscription) for details.
* To set or update the video layout during the recording, call the [`updateLayout`](/en/api-reference/api-ref/cloud-recording#updatelayout) method. See [Set Video Layout](/en/realtime-media/cloud-recording/build/customize-the-recording/layout) for details.
* [Common errors in cloud recording](/en/realtime-media/cloud-recording/reference/common-errors) lists common error codes and error messages in the response body.
* [Agora Cloud Recording RESTful API Callback Service](../reference/rest-api-overview) lists all the callback events of cloud recording.
* To learn more about the implementation steps and details of basic functions, you can refer to the following documents:
* [Individual recording](/en/realtime-media/cloud-recording/build/start-a-recording/individual-mode)
* [Composite recording](/en/realtime-media/cloud-recording/build/start-a-recording/composite-mode)
* [Web page recording](/en/realtime-media/cloud-recording/build/start-a-recording/webpage-mode)
* [Capture screenshots](/en/realtime-media/cloud-recording/build/start-a-recording/screen-capture)
## Next steps [#next-steps]
### Manage recorded files [#manage-recorded-files]
After the recording starts, the Agora server splits the recorded content into multiple TS/WebM files and keeps uploading them to the third-party cloud storage until the recording stops. You can refer to [Manage Recorded Files](/en/realtime-media/cloud-recording/build/process-recorded-files/manage-files) to learn about the naming rules, file sizes, and slicing rules of recording files.
### Token authentication [#token-authentication]
To ensure communication security, in a formal production environment, you need to generate tokens on your app server. See [Authenticate Your Users with Token](/en/realtime-media/cloud-recording/build/set-up-authentication/authentication-workflow).
# Agora skills (/en/realtime-media/cloud-recording/skills)
Agora skills is a set of structured reference files that give AI coding assistants deep knowledge of Agora's platform. When you ask your assistant to build something with Agora, it loads the relevant skill files covering products, APIs, and platform-specific code examples, so it can generate working code without guessing.
Skills includes integration with the [Agora MCP server](./mcp), which gives your assistant access to live Agora documentation. For installation instructions and supported tools, see the [Agora Skills repository](https://github.com/AgoraIO/skills).
### Installation [#installation]
Install Agora Skills using one of the following methods:
#### Skills CLI (recommended) [#skills-cli-recommended]
Run the following command:
```bash
npx skills add github:AgoraIO/skills
```
Skills activate automatically when your agent detects relevant tasks, for example, "build cloud recording workflows", "integrate Agora RTC", or "generate a token".
#### Manual installation [#manual-installation]
Clone the repository once and point your AI coding assistant to the skill files directly.
1. Clone the [Agora Skills repo](https://github.com/AgoraIO/skills.git):
```bash
git clone https://github.com/AgoraIO/skills.git ~/agora-skills
```
2. Point your AI assistant to `skills/agora/`.
Follow the instructions for your AI coding assistant:
Claude Code
Cursor
Windsurf
GitHub Copilot
Other tools
Run the following commands inside Claude Code:
* **User-level** (available across all your projects)
```bash
/plugin marketplace add AgoraIO/skills
/plugin install agora
```
* **Project-level** (shared with your team via version control)
```bash
/plugin marketplace add AgoraIO/skills
/plugin install agora --scope project
```
Copy or symlink `skills/agora/` into `.cursor/rules/`. For more information, see [Cursor skill directories](https://cursor.com/docs/skills#skill-directories).
Add `skills/agora/` to your Cascade context. For more information, see [Windsurf skills](https://docs.windsurf.com/windsurf/cascade/skills).
Reference via `@workspace` or add to `.github/copilot-instructions.md`. For more information, see [Create skills for Copilot in the CLI](https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/create-skills) or [Create skills for the Copilot coding agent](https://docs.github.com/en/copilot/how-tos/use-copilot-agents/coding-agent/create-skills).
The skill files are plain markdown. Point your tool to `skills/agora/` or load individual files directly. Use `SKILL.md` as the entry point.
# Flexible Classroom overview (/en/realtime-media/flexible-classroom/product-overview)
Agora's Flexible Classroom solution enables real-time, interactive virtual learning experiences. With features like video, audio, screen sharing, and messaging, it’s adaptable to a wide range of educational use-cases. Built on Agora’s low-latency, scalable platform, it provides a customizable foundation for creating engaging and collaborative online learning environments.
Scalable for one-on-one tutoring, group discussions, or large lecture halls, Flexible Classroom adapts to your specific needs. Customize the classroom experience with branded designs, tailored workflows, and role-based permissions. Advanced tools like attendance tracking, interactive features, and secure data handling ensure a seamless and engaging learning environment on web, desktop, or mobile platforms.
## Start building [#start-building]
## Product Features [#product-features]
Easily support one-to-one, small groups, or very large, collaborative lecture halls based on your business requirements.
Deploy an online classroom solution that is custom-branded and designed.
Tailor features and business logic to best suit your needs, whether on web, desktop, or mobile.
Easily manage classrooms and events, recordings, public resources, and user roles and permissions.
Agora is certified to the ISO and SOC 2 information security standards and meets privacy regulations like GDPR.
Track student attendance and monitor online participation like hand raise, live polling, pop-up quizzes, and whiteboard annotation.
# Demo quickstart (/en/realtime-media/flexible-classroom/quickstart)
Compelling content like animated presentations with embedded media helps actively engage students for longer in online classrooms. Agora Flexible Classroom helps you personalize distance learning using interactive video, shared whiteboards and other collaboration tools. With Flexible Classroom, you can quickly deploy effective online tutoring software that is customized with the features and branding your organization needs.
This page shows you how to quickly set up and launch a Flexible Classroom.
## Understand the tech [#understand-the-tech]
This section explains the workflow you implement to join a Flexible Classroom.

When an app client requests to join a Flexible Classroom, the app client and your app server interact with the Agora server in the following steps:
1. Your app client sends a request to your app server for a Signaling token.
2. Your app server generates a Signaling token using the Agora [App ID](./build/manage-agora-account.mdx), [App Certificate](./build/manage-agora-account.mdx), and a user ID. For details, see [Generate a Signaling token](./build/set-up-your-account-and-authentication/authentication-workflow.mdx).
3. Your app client calls an API with the following parameters to join a Flexible Classroom:
* *The user ID*: A unique string identifying a user, generated by your security system. This ID must be the same as the user ID you use for generating the Signaling token.
* *The room ID*: A string for identifying a classroom. When the first user joins a Flexible Classroom, Agora automatically creates a classroom with the room ID.
* *The Signaling token*: A credential for verifying the identity of the user when they join a Flexible Classroom.
## Prerequisites [#prerequisites]
In order to follow this procedure you must have:
* An Agora [account](./build/manage-agora-account.mdx) and [project](./build/manage-agora-account.mdx).
* A computer with Internet access. Ensure that no firewall is blocking your network communication.
* [Enabled Flexible Classroom](./build/set-up-your-account-and-authentication/enable-flexible-classroom) in Agora Console.
<_PlatformTabsGroup groupMode="structured" canonicalPlatform="web" platforms="["android","ios","web","electron"]" showTabs="true">
<_PlatformPanel platform="android">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="android" />
## Android [#android]
* Installed [Git](https://git-scm.com/downloads)
* Installed the [Java Development Kit](https://www.oracle.com/java/technologies/javase-downloads.html)
* Installed Android Studio 4.1 or above
* An Android device (not a simulator)
## Project setup [#project-setup]
To set up your Flexible Classroom project:
1. Clone the repository locally:
```bash
git clone https://github.com/AgoraIO-Community/CloudClass-Android.git
```
2. Update to the supported version of Flexible Classroom:
```bash
cd CloudClass-Android
git checkout release/2.8.11
```
## Implement a flexible classroom [#implement-a-flexible-classroom]
Follow these steps to configure and launch your Flexible Classroom from the downloaded source code:
1. **Import the CloudClass-Android project in Android Studio**
In Android Studio, navigate to the **File > New > Import Project...** menu option and select the folder named `CloudClass-Android` in the browse window.

2. **Sync the Android project**
Android Studio automatically performs a gradle sync to downloads the dependencies.
3. **Update the parameters**
Copy your *App ID* and *App Certificate* from Agora Console and replace the values of *agora\_app\_id* and *agora\_app\_cert* in `CloudClass-Android\app\src\main\res\values\string_config.xml`.
## Test your implementation [#test-your-implementation]
1. Connect a physical Android device to your development device.
2. In Android Studio, click **Run app**. A moment later, the project is installed on your device.
3. In the login screen, enter the **Room**, your **Name**, the class **Type**, your **Role** and your current **Region**, then click **Enter**.

You are logged in to Flexible Classroom and presented with the following UI:

4. Install Flexible Classroom on a second Android device. Login with the same credentials but set **Role** to `Student`.
5. The teacher and student can now interact and communicate using Flexible Classroom.
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="ios">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="ios" />
## iOS [#ios]
* Installed [CocoaPods](https://guides.cocoapods.org/using/getting-started.html#getting-started) version 1.10 or later.
* If you use Swift, make sure you have version `5.0` or later.
## Project setup [#project-setup-1]
To set up your Flexible Classroom project:
1. Clone the repository:
```bash
git clone https://github.com/AgoraIO-Community/flexible-classroom-ios.git
```
2. Update to the supported version of Flexible Classroom:
```bash
cd flexible-classroom-ios
git checkout release/2.8.11
```
## Implement a flexible classroom [#implement-a-flexible-classroom-1]
To configure and launch your Flexible Classroom from the downloaded source code:
1. Navigate to the `CloudClass-iOS/App` directory:
```bash
cd App
```
2. Install project dependencies using the following command:
```bash
pod install
```
You see the dependency package install.

3. To open the project in Xcode, use the following command:
```bash
open AgoraEducation.xcworkspace
```
You see the following:

4. In Signing & Capabilities under the project **TARGETS**, check **Automatically manage signing**, and configure your Apple developer account and Bundle Identifier.

## Test your implementation [#test-your-implementation-1]
1. Connect a physical iOS device to your development device.
2. In Xcode, click **Run**. A moment later, the project is installed on your device.
3. On the login screen, type in a room name and user name. Select a class type, set the user role to `Teacher` and then press **Enter**.

You are logged in to Flexible Classroom and presented with the following UI:

4. Install Flexible Classroom on a second iOS device. Login with the same credentials but this time set the user role to `Student`.
5. The `Teacher` and `Student` can now interact and communicate using Flexible Classroom features.
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="web">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="web" />
## Web [#web]
* Installed [Git](https://git-scm.com/downloads)
## Project setup [#project-setup-2]
To set up and run your Flexible Classroom project, you need the following tools:
* `Node.js` JavaScript runtime environment for building and running the web project.
* `yarn`: A package manager.
* `nvm`: (Optional) Version management command-line tool for `Node.js`.
### Install the development tools [#install-the-development-tools]
To prepare your development environment:
1. [Download](https://git-scm.com/downloads) Git.
2. [Download](https://nodejs.org/en) and install Node.js. It is recommended to use Node.js 16. Node.js 18 and above versions are not supported yet.
3. Install Yarn:
* If you have `Node.js` `16.10` or later, you can directly enable `yarn` with the following command:
```bash
corepack enable
```
* If you have a version of `Node.js` earlier than 16.10, you need to install `corepack` first and then enable `yarn` with the following command:
```bash
npm i -g corepack enable
```
4. (Optional) To install `nvm`, run the following command:
```bash
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash
```
5. (Optional) When Electron installation fails, you can install it by setting the Electron mirror address:
```bash
export ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/
export ELECTRON_CUSTOM_DIR=v12.0.0
export ELECTRON_CUSTOMDIR=v12.0.0
```
### Download the source code [#download-the-source-code]
To set up your Flexible Classroom project:
1. Clone the repository locally by running the following command:
```bash
git clone https://github.com/AgoraIO-Community/flexible-classroom-desktop.git
```
2. After the download is complete, navigate to the folder:
```bash
cd flexible-classroom-desktop
```
3. Best practice is to switch to the latest release.
To switch to the branch for version `2.9.0`, run the command:
```bash
git checkout release/2.9.0
```
## Implement a Flexible Classroom [#implement-a-flexible-classroom-2]
Follow these steps to launch a Flexible Classroom:
1. Open a terminal window and navigate to the `Flexible-Classroom-Desktop` folder. Run the following command to install the project dependencies:
```bash
yarn install:packages
```
If the installation dependency times out, switch to an available mirror source:
```bash
# Switch to a mirror
yarn config set registry https://registry.npmmirror.com/
# Install dependencies
yarn install:packages
```
2. Copy the App ID and App Certificate from Agora Console and update the corresponding parameters in the `.env` file:
```javascript
REACT_APP_AGORA_APP_ID={your appid}
REACT_APP_AGORA_APP_CERTIFICATE={your app certificate}
```
To facilitate your testing, the `Flexible-Classroom-Desktop` project contains a Signaling Token generator, which can generate a temporary Signaling Token with the App ID and App Certificate you provide. When your project goes live, you must deploy a Signaling Token generator on your server to ensure security.
3. Run the following command to launch the web client in different development modes:
```bash
# Start Flexible Classroom Demo debugging
yarn dev
# Start AgoraEduSDK debugging
yarn dev:classroom
# Start Proctor SDK debugging
yarn dev:proctor
# Start FcrUIScene debugging
yarn dev:scene
```
If you use Node.js 17, you may encounter the `ERR_OSSL_EVP_UNSUPPORTED` error. This is because Node.js 17 has switched to OpenSSL 3.0 and conflicts with the encryption algorithm or key being used in some dependencies in the project. You can try the following two solutions:
* Add `NODE_OPTION` parameters `--openssl-legacy-provider`
```bash
# Find the Node.js configuration file packages/agora-demo-app/package.json
# To modify the startup command under the script field, add the NODE_OPTION parameter, for example:
# Original command
"dev": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=6144 FCR_ENTRY=demo webpack serve --config ./webpack.dev.js"
# Change to
"dev": "cross-env NODE_ENV=development NODE_OPTIONS=\"--max_old_space_size=6144 --openssl-legacy-provider\" FCR_ENTRY=demo webpack serve --config ./webpack.dev.js"
# Then start the project
yarn dev
```
* Use nvm to downgrade the Node.js version to 16
```bash
# Use nvm to manage Node.js versions
# Install nvm
yarn global add nvm
# Install Node.js version 16 using nvm
nvm install 16
# Use Node.js version 16
nvm use 16
# Start Flexible Classroom
yarn dev
```
## Test your implementation [#test-your-implementation-2]
1. Open the browser and navigate to `http://localhost:3000`. You see the login page of Flexible Classroom.
2. To join a classroom, type in a room name and user name. Select a class type, set the user role to `Teacher` and then press **Enter**.
You are logged in to Flexible Classroom.
3. Install Flexible Classroom on a second device. Login with the same credentials but this time set the user role to `Student`.
4. The `Teacher` and `Student` can now interact and communicate using Flexible Classroom features.
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="electron">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="electron" />
## Electron [#electron]
## Set up the development environment [#set-up-the-development-environment]
* Installed [Git](https://git-scm.com/downloads)
## Project setup [#project-setup-3]
To set up and run your Flexible Classroom project, you need the following tools:
* `Node.js` JavaScript runtime environment for building and running the web project.
* `yarn`: A package manager.
* `nvm`: (Optional) Version management command-line tool for `Node.js`.
### Install the development tools [#install-the-development-tools-1]
To prepare your development environment:
1. [Download](https://git-scm.com/downloads) Git.
2. [Download](https://nodejs.org/en) and install Node.js. It is recommended to use Node.js 16. Node.js 18 and above versions are not supported yet.
3. Install Yarn:
* If you have `Node.js` `16.10` or later, you can directly enable `yarn` with the following command:
```bash
corepack enable
```
* If you have a version of `Node.js` earlier than 16.10, you need to install `corepack` first and then enable `yarn` with the following command:
```bash
npm i -g corepack enable
```
4. (Optional) To install `nvm`, run the following command:
```bash
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash
```
5. (Optional) When Electron installation fails, you can install by setting the Electron mirror address:
```bash
export ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/
export ELECTRON_CUSTOM_DIR=v12.0.0
export ELECTRON_CUSTOMDIR=v12.0.0
```
### Download the source code [#download-the-source-code-1]
To set up your Flexible Classroom project:
1. Clone the repository locally by running the following command:
```bash
git clone https://github.com/AgoraIO-Community/flexible-classroom-desktop.git
```
2. After the download is complete, navigate to the folder:
```bash
cd flexible-classroom-desktop
```
3. Best practice is to switch to the latest release.
To switch to the branch for version `2.9.0`, run the command:
```bash
git checkout release/2.9.0
```
Agora recommends that you switch to the latest release branch.
## Launch a Flexible Classroom [#launch-a-flexible-classroom]
Follow these steps to launch a Flexible Classroom:
1. Open a terminal window and navigate to the `Flexible-Classroom-Desktop` folder. Run the following command to install the project dependencies:
```bash
yarn install:packages
```
If the installation dependency times out, switch to an available mirror source:
```bash
# Switch to a mirror
yarn config set registry https://registry.npmmirror.com/
# Install dependencies
yarn install:packages
```
2. Copy the App ID and App Certificate from Agora Console and update the corresponding parameters in `packages/agora-classroom-sdk/.env`:
```javascript
REACT_APP_AGORA_APP_ID={your appid}
REACT_APP_AGORA_APP_CERTIFICATE={your app certificate}
```
To facilitate your testing, the `flexible-classroom-desktop` project contains a Signaling Token generator, which can generate a temporary Signaling Token with the App ID and App Certificate you provide. When your project goes live, you must deploy a Signaling Token generator on your server to ensure security.
3. Refer to the following steps to run Flexible Classroom on macOS or Windows:
* **macOS**
To build the project, run the following command in the root directory of the `Flexible-Classroom-Desktop` project:
```bash
yarn dev:electron
```
* **Windows**
1. Replace `"agora_electron"` in `package.json` file in the `packages/agora-demo-app` directory with the following code:
```javascripton
"agora_electron": {
"electron_version": "12.0.0",
"prebuilt": true,
"platform": "win32",
"arch": "ia32"
},
```
2. To install Electron 12.0.0, run the following command:
```bash
npm install electron@12.0.0 --arch=ia32 --save-dev
```
3. To build the project, run the following command in the root directory of the `Flexible-Classroom-Desktop` project:
```bash
yarn dev:electron
```
## Test your implementation [#test-your-implementation-3]
1. Run the project on your Windows or macOS device.
2. To join a classroom, type in a room name and user name. Select a class type, set the user role to `Teacher` and then press **Enter**.
You are logged in to Flexible Classroom.
3. Install Flexible Classroom on a second device. Login with the same credentials but this time set the user role to `Student`.
4. The `Teacher` and `Student` can now interact and communicate using Flexible Classroom features.
<_PlatformProcessedMarker close="true" />
## References [#references]
To ensure communication security in a test or production environment, use a token server to generate tokens. See [Secure authentication with tokens](./build/set-up-your-account-and-authentication/authentication-workflow.mdx).
# SDK quickstart (/en/realtime-media/im/get-started-sdk)
Instant messaging enhances user engagement by enabling users to connect and form a community within the app. Increased engagement can lead to increased user satisfaction and loyalty to your app. An instant messaging feature can also provide real-time support to users, allowing them to get help and answers to their questions quickly. The Chat SDK enables you to embed real-time messaging in any app, on any device, anywhere.
This page guides you through implementing peer-to-peer messaging into your app using the Chat SDK.
## Understand the tech [#understand-the-tech]
The following figure shows the workflow of sending and receiving peer-to-peer messages using Chat SDK.
Chat SDK workflow

1. Clients retrieve an authentication token from your app server.
2. Users log in to Chat using the app key, their user ID, and token.
3. Clients send and receive messages through Chat as follows:
1. Client A sends a message to Client B. The message is sent to the Agora Chat server.
2. The server delivers the message to Client B. When Client B receives a message, the SDK triggers an event.
3. Client B listens for the event to read and display the message.
## Prerequisites [#prerequisites]
In order to follow the procedure on this page, you must have:
* A valid [Agora account](/en/realtime-media/im/get-started/manage-agora-account#create-an-agora-account).
* An [Agora project](/en/realtime-media/im/get-started/manage-agora-account#create-an-agora-project) for which you have [enabled Chat](./enable#enable-).
* The [App Key](./enable#get-chat-project-information) for the project.
* Internet access.
Ensure that no firewall is blocking your network communication.
<_PlatformTabsGroup groupMode="structured" canonicalPlatform="web" platforms="["android","ios","web","flutter","react-native","unity","windows"]" showTabs="true">
<_PlatformPanel platform="android">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="android" />
* An Android emulator or a physical Android device.
* Android Studio 3.6 or higher.
* Java Development Kit (JDK). You can refer to the [Android User Guide](https://developer.android.com/studio/write/java8-support) for applicable versions.
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="ios">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="ios" />
* Xcode. This page uses Xcode 13.0 as an example.
* A device running iOS 10 or later.
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="web">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="web" />
* A Windows or macOS computer that meets the following requirements:
* A browser supported by the Agora Chat SDK:
* Internet Explorer 9 or later
* FireFox 10 or later
* Chrome 54 or later
* Safari 6 or later
* Access to the Internet. If your network has a firewall, follow the instructions in [Firewall Requirements](https://docs.agora.io/en/Agora%20Platform/firewall) to access Agora services.
* [Node.js and npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="flutter">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="flutter" />
* Flutter 2.10 or higher.
* Dart 2.16 or higher.
* If your target platform is iOS:
* macOS
* Xcode 12.4 or higher with Xcode Command Line Tools
* CocoaPods
* An iOS emulator or a physical iOS device running iOS 10.0 or higher
* If your target platform is Android:
* macOS or Windows
* Android Studio 4.0 or higher with JDK 1.8 or higher
* An Android emulator or a physical Android device running Android SDK API 21 or higher
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="react-native">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="react-native" />
If your target platform is iOS:
* macOS 10.15.7 or later
* Xcode 12.4 or later, including command line tools
* React Native 0.66.5 or later
* NodeJs 16 or later, including npm package management tool
* CocoaPods package management tool
* Yarn compile and run tool
* Watchman debugging tool
* A physical or virtual mobile device running iOS 10.0 or later
If your target platform is Android:
* macOS 10.15.7 or later, or Windows 10 or later
* Android Studio 4.0 or later, including JDK 1.8 or later
* React Native 0.66.5 or later
* CocoaPods package management tool if your operating system is macOS.
* Powershell 5.1 or later if your operating system is Windows.
* NodeJs 16 or later, including npm package management tool
* Yarn compile and run tool
* Watchman debugging tool
* A physical or virtual mobile device running Android 6.0 or later
For more information, see [Setting up the environment](https://reactnative.dev/docs/environment-setup).
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="unity">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="unity" />
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="windows">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="windows" />
* A device running Windows 7 or higher.
* Visual Studio IDE 2019 or later
* .NET Framework 4.5.2 or later
<_PlatformProcessedMarker close="true" />
## Project setup [#project-setup]
<_PlatformTabsGroup groupMode="structured" canonicalPlatform="web" platforms="["android","ios","web","flutter","react-native","unity","windows"]" showTabs="true">
<_PlatformPanel platform="android">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="android" />
To integrate Chat into your app, do the following:
1. Method 1: Use `mavenCentral` to automatically integrate:
1. In Android Studio, create a new **Phone and Tablet** [Android project](https://developer.android.com/studio/projects/create-project) with an **Empty Activity**. Choose **Java** as the project language, and ensure that the **Minimum SDK** version is set to `21` or higher.
Android Studio automatically starts gradle sync. Wait for the sync to succeed before you continue.
2. Add Chat SDK to your project dependencies.
To add the SDK to your project:
1. In `/Gradle Scripts/build.gradle (Module: .app)`, add the following line under `dependencies`:
```text
dependencies {
...
implementation 'io.agora.rtc:chat-sdk:'
}
```
Replace `` with the version number for the latest Chat SDK release, for example `1.3.1`. You can obtain the latest version information using [Maven Central Repository Search](https://central.sonatype.com/artifact/io.agora.rtc/chat-sdk/versions).
2. To download the SDK from Maven Central, press **Sync Now**.
3. Add permissions for network and device access.
To add the necessary permissions, in `/app/Manifests/AndroidManifest.xml`, add the following permissions after ``:
```xml
```
4. Prevent code obfuscation.
In `/Gradle Scripts/proguard-rules.pro`, add the following line:
```java
-keep class io.agora.** {*;}
-dontwarn io.agora.**
```
2. Method 2: Dynamically load .so library files
In order to reduce the size of the application installation package, the SDK provides `ChatOptions#setNativeLibBasePath` method to support dynamic loading of the `.so` files required by the SDK. Taking SDK 1.3.0 as an example, the `.so` file includes two files: `libcipherdb.so` and `.libhyphenate.so`. The steps to implement this function are as follows:
1. Download the latest version of the SDK and unzip it.
2. Integrate the `agorachat_1.3.0.jar` file into your project.
3. Upload `.so` files for all schemas to your server and ensure that the application can download `.so` files for the target schemas over the network.
4. When the application runs, it checks whether the `.so` file exists. If not found, the app downloads the `.so` file and saves it to your custom app's private directory.
5. When calling `ChatClient#init`, set the app private directory where the `.so` file is located as a parameter into the `ChatOptions#setNativeLibBasePath` method.
6. The SDK will automatically load `.so` files from the specified path upon initialization.
```java
// Assume that you have put two .so libraries, `libcipherdb.so` and `libagora-chat-sdk.so` in the /data/data/packagename/files directory of the app.
String filesPath = mContext.getFilesDir().getAbsolutePath();
ChatOptions options = new ChatOptions();
options.setNativeLibBasePath(filesPath);
ChatClient.getInstance().init(mContext, options);
```
This method is applicable when you integrate the SDK manually but not when you integrate the SDK with Maven Central.
You can specify the path of `.so` files with the path parameter in the `ChatOptions#setNativeLibBasePath` method. The path must be a valid and private directory of the app.
If you set this parameter, the SDK will use `System.load` to load the `.so` library from the directory you specify, so that the app dynamically loads the required `.so` files when it runs, thereby reducing the package size.
If you do not set this parameter or set it to `null`, the SDK will use `System.load` to load the `.so` library from the default path when compiling the app, thus increasing the package size compared to the previous method.
Since March 6, 2024, using this method to reduce the app size no longer meets the requirements of Google Play. For details, refer to the [Developer Program Policies](https://support.google.com/googleplay/android-developer/answer/14906471?hl=en\&visit_id=638555962315571301-1274648059\&rd=1) issued by Google. If your app needs to be listed on Google Play, please try other ways to reduce the app size. For details, see [App size optimization](/en/realtime-media/rtc/best-practices/app-size-optimization#dynamically-load-so-files).
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="ios">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="ios" />
To integrate Chat into your app, do the following:
1. [Create a new project](https://help.apple.com/xcode/mac/current/#/dev07db0e578) for this device app using the **App** template. Select the **Storyboard** Interface and **Swift** Language.
If you have not already added team information, click **Add account…**, input your Apple ID, then click **Next**.
2. [Enable automatic signing](https://help.apple.com/xcode/mac/current/#/dev23aab79b4) for your project.
[Set the target devices](https://help.apple.com/xcode/mac/current/#/deve69552ee5) to deploy your iOS app to an iPhone or iPad.
3. Add project permissions for microphone and camera usage:
1. Open **Info** in the project navigation panel, then add the following properties to the [Information Property List](https://help.apple.com/xcode/mac/current/#/dev3f399a2a6):
| Key | Type | Value |
| ---------------------------- | ------ | ---------------------- |
| NSMicrophoneUsageDescription | String | Access the microphone. |
| NSCameraUsageDescription | String | Access the camera. |
4. Integrate Agora Chat SDK into your project:
1. In Xcode, click **File** > **Add Packages...**, then paste the following link in the search:
```
https://github.com/AgoraIO/AgoraChat_iOS.git
```
You see the available Agora packages. In **AgoraChat\_iOS**, specify the latest Chat SDK version, for example `1.0.9`. You can obtain the latest version information in the [release notes](./reference/release-notes).
2. Click **Add Package**. In the new window, click **Add Package**.
You see **AgoraChat** in **Package Dependencies** for your project.
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/AgoraChat_PrivacyInfo.xcprivacy) file that you can include in your project. If you are using Chat SDK 1.2 or earlier, refer to [Add a privacy manifest file](#add-a-privacy-manifest-file) for details.
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="web">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="web" />
To create the environment necessary to add peer-to-peer messaging into your app, do the following:
1. Use VITE to create a new project. If VITE is not installed, run `npm i vite -g` to install it. Then, run `npm create vite@latest agora_quickstart`. In this example, we choose "React + JavaScript" to create the project.
The project directory now has the following structure:
```text
agora_quickstart
├─ index.html
├─ main.js
└─ package.json
```
2. Integrate the Agora Chat SDK into your project through npm.
Add 'agora-chat' and 'vite' to the 'package.json' file.
```json
{
"name": "agora_quickstart",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies":{
"agora-chat": "latest"
},
"devDependencies": {
"vite": "^3.0.7"
}
}
```
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="flutter">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="flutter" />
To integrate Chat into your app, do the following:
1. **Set up a Flutter environment for a Chat project**
In the terminal, run the following command:
```bash
flutter doctor
```
Flutter checks your development device and helps you [set up](https://docs.flutter.dev/get-started/editor) your local development environment. Make sure that your system passes all the checks.
2. **Create a new Flutter app**
In the IDE of your choice, create a new [Flutter Application project](https://docs.flutter.dev/development/tools/android-studio#creating-a-new-project).
You can also create a new project from the terminal using the following command:
```bash
flutter create <--insert project name-->
```
3. **Add Chat SDK to your project**
Add the following line to the `/pubspec.yaml` file under `dependencies`:
```yaml
dependencies:
...
# For x.y.z, fill in a specific SDK version number. For example, 1.0.9
agora_chat_sdk: ^x.y.z
...
```
To get the latest device Chat SDK version number, visit [pub.dev](https://pub.dev/packages?q=agora_chat_sdk).
You can also add the Chat SDK dependency to your project by running the following command in the project folder:
```bash
flutter pub add agora_chat_sdk
```
4. **Use the Flutter framework to download dependencies to your app**
Open a terminal window and execute the following command in the project folder:
```bash
flutter pub get
```
5. **Add platform specific requirements**
Update the minimum version requirements for your target platforms as follows:
* **Android**
* In the `/android/app/build.gradle` file, set the Android `minSdkVersion` to `21` under `defaultConfig {`.
```json
android {
defaultConfig {
minSdkVersion 21
}
}
```
* In the `/android/app/proguard-rules.pro` file, add the following lines to prevent code obfuscation:
```bash
-keep class com.hyphenate.** {*;}
-dontwarn com.hyphenate.**
```
* **iOS**
Open the `/ios/Runner.xcodeproj` file in **Xcode**, and select **TARGETS** > **Runner** in the left sidebar. In the **General** > **Deployment Info** section, set the minimum iOS version to `iOS 10.0`.
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="react-native">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="react-native" />
Follow these steps to create a React Native project and add Agora Chat into your app.
1. Prepare the development environment according to your development system and target platform.
2. In your terminal, navigate to the directory where you want to create the project, and run the following command to create a React Native project:
```sh
npx @react-native-community/cli@latest init --version 0.76 simple_demo
```
The created project name is `simple_demo`.
Initialize the project:
```sh
cd simple_demo
yarn set version 1.22.19
yarn
```
You can use npm or other tools.
3. In the terminal, run the following command to add dependencies:
```sh
yarn add react-native-agora-chat
```
4. Initialize the native part.
For iOS platform, use `cocoapods` to initialize:
```sh
cd ios && pod install && cd ..
```
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="unity">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="unity" />
1. **Create a Unity project**:
1. In Unity Hub, select **Projects**, then click **New Project**.
2. In **All templates**, select **3D**. Set the **Project name** and **Location**, then click **Create Project**.
Your project opens in Unity Editor.
2. **Integrate Chat SDK into your project**:
1. Unzip [Chat SDK for Unity](/en/api-reference/sdks?product=chat\&platform=unity) to a local folder:
2. In Unity, click **Assets > Import Package > Custom Package**.
3. Navigate to the Chat SDK package and click *Open*.
4. In **Import Unity Package**, click **Import**.
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="windows">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="windows" />
This section shows how to integrate Chat SDK into your project.
1. Create a Windows project
1. Open **Visual Studio** and [Create a C# WPF app](https://learn.microsoft.com/en-us/dotnet/desktop/wpf/get-started/create-app-visual-studio?view=netdesktop-7.0#create-a-wpf-app) named `AgoraWindowsChat`.
2. Set the **Active solution configuration** as **Debug** and **Active solution platform** as **x64**.
2. Integrate the Chat SDK
1. [Download](/en/api-reference/sdks?product=chat\&platform=windows) the Chat SDK NuGet package.
2. In Solution Explorer, right-click `` and select **Manage NuGet Packages...**.
3. On the `NuGet: ` page, select the **Gear** icon at the top right.
4. In **Options**,
1. Click **+** at the top right, then select the new package source.
2. Set **Name**, then set **Source** to the parent directory of the Nuget package resides.
3. Click **OK**.
5. Go back to the `NuGet: ` page, open the **Package source** drop-down list at the top right, then
select the package you just added.
6. In **Browse** on the `NuGet: ` page, select **agora\_chat\_sdk**, then click **Install**.
If you don't see **agora\_chat\_sdk**, refresh the page.
7. In **Preview Changes**, click **OK**
You have installed Chat SDK.
<_PlatformProcessedMarker close="true" />
## Implement peer-to-peer messaging [#implement-peer-to-peer-messaging]
This section shows how to use the Chat SDK to implement peer-to-peer messaging in your app, step by step.
<_PlatformTabsGroup groupMode="structured" canonicalPlatform="web" platforms="["android","ios","web","flutter","react-native","unity","windows"]" showTabs="true">
<_PlatformPanel platform="android">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="android" />
### Create the UI [#create-the-ui]
In the quickstart app, you create a simple UI that consists of the following elements:
* A `Button` to log in or out of Agora Chat.
* An `EditText` box to specify the recipient user ID.
* An `EditText` box to enter a text message.
* A `Button` to send the text message.
* A scrollable layout to display sent and received messages.
To add the UI framework to your device project, open `app/res/layout/activity_main.xml` and replace the content with the following:
```xml
```
You see `Cannot resolve symbol` errors in your IDE. This is because this layout refers to methods that you create later.
### Handle the system logic [#handle-the-system-logic]
Import the necessary classes, and add a method to show status updates to the user.
1. **Import the relevant Agora and Android classes**
In `/app/java/com.example./MainActivity`, add the following lines after `package com.example.`:
```java
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.view.View;
import android.widget.Toast;
import android.graphics.Color;
import android.view.Gravity;
import android.view.inputmethod.InputMethodManager;
import android.util.Log;
import java.util.List;
import io.agora.CallBack;
import io.agora.ConnectionListener;
import io.agora.MessageListener;
import io.agora.chat.ChatClient;
import io.agora.chat.ChatMessage;
import io.agora.chat.ChatOptions;
import io.agora.chat.TextMessageBody;
```
2. **Log events and show status updates to your users**
In the `MainActivity` class, add the following method before `onCreate`.
```javascript
private void showLog(String text) {
// Show a toast message
runOnUiThread(() ->
Toast.makeText(getApplicationContext(), text, Toast.LENGTH_SHORT).show());
// Write log
Log.d("AgoraChatQuickStart", text);
}
```
### Send and receive messages [#send-and-receive-messages]
When a user opens the app, you instantiate and initialize a `ChatClient`. When the user taps the **Join** button, the app logs in to Agora Chat. When a user types a message in the text box and then presses **Send**, the typed message is sent to Agora Chat. When the app receives a message from the server, the message is displayed in the message list. This simple workflow enables you to rapidly build a Chat client with basic functionality.
The following figure shows the API call sequence for implementing this workflow.
API call sequence

To implement this workflow in your app, take the following steps:
1. **Declare variables**
In the `MainActivity` class, add the following declarations:
```java
private String userId = "";
private String token = "";
private String appKey = "";
private ChatClient agoraChatClient;
private boolean isJoined = false;
EditText editMessage;
```
2. **Set up Chat when the app starts**
When the app starts, you create an instance of the `ChatClient` and set up callbacks to handle Chat events.
To do this, replace the `onCreate` method in the `MainActivity` class with the following:
```java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setupChatClient(); // Initialize the ChatClient
setupListeners(); // Add event listeners
// Set up UI elements for code access
editMessage = findViewById(R.id.etMessageText);
}
```
3. **Instantiate the `ChatClient`**
To implement peer-to-peer messaging, you use Chat SDK to initialize a `ChatClient` instance. In the `MainActivity` class, add the following method before `onCreate`.
```java
private void setupChatClient() {
ChatOptions options = new ChatOptions();
if (appKey.isEmpty()) {
showLog("You need to set your AppKey");
return;
}
options.setAppKey(appKey); // Set your app key in options
agoraChatClient = ChatClient.getInstance();
agoraChatClient.init(this, options); // Initialize the ChatClient
agoraChatClient.setDebugMode(true); // Enable debug info output
}
```
4. **Handle and respond to Chat events**
To receive notification of Chat events such as connection, disconnection, and token expiration, you add a `ConnectionListener`. To handle message delivery and new message notifications, you add a `MessageListener`. When you receive the `onMessageReceived` notification, you display the message to the user.
In `/app/java/com.example./MainActivity`, add the following method after `setupChatClient`:
```java
private void setupListeners() {
// Add message event callbacks
agoraChatClient.chatManager().addMessageListener(new MessageListener() {
@Override
public void onMessageReceived(List messages) {
for (ChatMessage message : messages) {
runOnUiThread(() ->
displayMessage(((TextMessageBody) message.getBody()).getMessage(),
false)
);
showLog("Received a " + message.getType().name()
+ " message from " + message.getFrom());
}
}
});
// Add connection event callbacks
agoraChatClient.addConnectionListener(new ConnectionListener() {
@Override
public void onConnected() {
showLog("Connected");
}
@Override
public void onDisconnected(int error) {
if (isJoined) {
showLog("Disconnected: " + error);
isJoined = false;
}
}
@Override
public void onLogout(int errorCode) {
showLog("User logging out: " + errorCode);
}
@Override
public void onTokenExpired() {
// The token has expired
}
@Override
public void onTokenWillExpire() {
// The token is about to expire. Get a new token
// from the token server and renew the token.
}
});
}
```
5. **Log in to Agora Chat**
When a user clicks **Join**, your app logs in to Agora Chat. When a user clicks **Leave**, the app logs out of Agora Chat.
To implement this logic, in the `MainActivity` class, add the following method before `onCreate`:
```java
public void joinLeave(View view) {
Button button = findViewById(R.id.btnJoinLeave);
if (isJoined) {
agoraChatClient.logout(true, new CallBack() {
@Override
public void onSuccess() {
showLog("Sign out success!");
runOnUiThread(() -> button.setText("Join"));
isJoined = false;
}
@Override
public void onError(int code, String error) {
showLog(error);
}
});
} else {
agoraChatClient.loginWithToken(userId, token, new CallBack() {
@Override
public void onSuccess() {
showLog("Signed in");
isJoined = true;
runOnUiThread(() -> button.setText("Leave"));
}
@Override
public void onError(int code, String error) {
if (code == 200) { // Already joined
isJoined = true;
runOnUiThread(() -> button.setText("Leave"));
} else {
showLog(error);
}
}
});
}
}
```
6. **Send a message**
To send a message to Agora Chat when a user presses the **Send** button, add the following method to the `MainActivity` class, before `onCreate`:
```java
public void sendMessage(View view) {
// Read the recipient name from the EditText box
String toSendName = ((EditText) findViewById(R.id.etRecipient)).getText().toString().trim();
String content = editMessage.getText().toString().trim();
if (toSendName.isEmpty() || content.isEmpty()) {
showLog("Enter a recipient name and a message");
return;
}
// Create a ChatMessage
ChatMessage message = ChatMessage.createTextSendMessage(content, toSendName);
// Set the message callback before sending the message
message.setMessageStatusCallback(new CallBack() {
@Override
public void onSuccess() {
showLog("Message sent");
runOnUiThread(() -> {
displayMessage(content, true);
// Clear the box and hide the keyboard after sending the message
editMessage.setText("");
InputMethodManager inputMethodManager = (InputMethodManager)getSystemService(INPUT_METHOD_SERVICE);
inputMethodManager.hideSoftInputFromWindow(editMessage.getApplicationWindowToken(),0);
});
}
@Override
public void onError(int code, String error) {
showLog(error);
}
});
// Send the message
agoraChatClient.chatManager().sendMessage(message);
}
```
7. **Display chat messages**
To display the messages the current user has sent and received in your app, add the following method to the `MainActivity` class:
```java
void displayMessage(String messageText, boolean isSentMessage) {
// Create a new TextView
final TextView messageTextView = new TextView(this);
messageTextView.setText(messageText);
messageTextView.setPadding(10,10,10,10);
// Set formatting
LinearLayout messageList = findViewById(R.id.messageList);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.MATCH_PARENT);
if (isSentMessage) {
params.gravity = Gravity.END;
messageTextView.setBackgroundColor(Color.parseColor("#DCF8C6"));
params.setMargins(100,25,15,5);
} else {
messageTextView.setBackgroundColor(Color.parseColor("white"));
params.setMargins(15,25,100,5);
}
// Add the message TextView to the LinearLayout
messageList.addView(messageTextView, params);
}
```
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="ios">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="ios" />
### Create the UI [#create-the-ui-1]
In the quickstart app, you create a simple UI that consists of the following elements:
* A `UIButton` to log in or out of the Chat.
* A `UITextField` box to specify the recipient user ID.
* A `UITextField` box to enter a text message.
* A `UIButton` to send the text message.
* A `UITextView` to display sent and received messages.
To create this user interface, in the `ViewController` class:
1. **Add the UI elements you need**
Add the following declarations at the top of the class.
```swift
var btnJoinLeave: UIButton!
var etRecipient: UITextField!
var scrollView: UITextView!
var etMessageText: UITextField!
var btnSendMessage: UIButton!
```
2. **Configure the UI elements in your interface**
Add the following below `super.viewDidLoad()` inside the `viewDidLoad` method:
```swift
btnJoinLeave = UIButton(type: .system)
btnJoinLeave.frame = CGRect(x: 60, y: 100, width: 250, height: 30)
btnJoinLeave.setTitle("Join", for: .normal)
btnJoinLeave.addTarget(self, action: #selector(joinLeave), for: .touchUpInside)
self.view.addSubview(btnJoinLeave)
etRecipient = UITextField(frame: CGRect(x: 100, y: 150, width: 300, height: 30))
etRecipient.placeholder = "Enter recipient user ID"
self.view.addSubview(etRecipient)
scrollView = UITextView(frame: CGRect(x: 130, y: 200, width: 300, height: 100))
scrollView.isEditable = false
self.view.addSubview(scrollView)
etMessageText = UITextField(frame: CGRect(x: 80, y: 320, width: 200, height: 30))
etMessageText.placeholder = "Message"
self.view.addSubview(etMessageText)
btnSendMessage = UIButton(type: .system)
btnSendMessage.frame = CGRect(x: 270, y: 320, width: 80, height: 30)
btnSendMessage.setTitle(">>", for: .normal)
btnSendMessage.addTarget(self, action: #selector(sendMessage), for: .touchUpInside)
self.view.addSubview(btnSendMessage)
```
### Handle the system logic [#handle-the-system-logic-1]
Import the necessary classes, and add a method to show status updates to the user.
1. **Import the Chat SDK class**
In `ViewController`, add the provided code sample after `import UIKit`:
```swift
import AgoraChat
```
If Xcode does not recognize this import, click **File** > **Packages** > **Reset Package Caches**.
2. **Log events and show status updates to your users**
In `ViewController`, add the following method after the `viewDidLoad` function:
```swift
func showLog(_ text: String) -> Void {
DispatchQueue.main.async {
let alert = UIAlertController(title: "", message: text, preferredStyle: .alert)
self.present(alert, animated: true)
alert.dismiss(animated: true, completion: nil)
}
}
```
### Send and receive messages [#send-and-receive-messages-1]
When a user opens the app, you instantiate and initialize a `ChatClient`. When the user taps the **Join** button, the app logs in to the Chat. When a user types a message in the text box and then presses **Send**, the typed message is sent to the Chat. When the app receives a message from the server, the message is displayed in the message list. This simple workflow enables you to rapidly build a Chat client with basic functionality.
To implement this workflow in your app, take the following steps:
1. **Declare variables**
In the `ViewController` class, add the following declarations at the top:
```swift
var userId = ""
var token = ""
var appKey = ""
var agoraChatClient: AgoraChatClient!
var isJoined: Bool = false
```
2. **Set up Chat when the app starts**
When the app starts, you create an instance of the `AgoraChatClient`. To do this, add the following to `viewDidLoad`:
```swift
setupChatClient() // Initialize the ChatClient
```
3. **Instantiate the `AgoraChatClient`**
To implement peer-to-peer messaging, you use Chat SDK to initialize an `AgoraChatClient` instance. In the `ViewController` class, add the following method after the `viewDidLoad` function:
```swift
func setupChatClient() {
if (appKey.isEmpty) {
showLog("You need to set your AppKey")
return
}
let options = AgoraChatOptions(appkey: appKey) // Create AgoraChatOptions with your app key
agoraChatClient = AgoraChatClient.shared()
options.enableConsoleLog = true // Enable printing logs on console
agoraChatClient.initializeSDK(with: options) // Initialize the AgoraChatClient
agoraChatClient.chatManager?.add(self, delegateQueue: nil) // Adds the chat delegate to receive messages
}
```
4. **Handle and respond to Chat events**
The `AgoraChatClientDelegate` implements callbacks to receive notification of Chat events such as a connection state change, and a token expiration. The `AgoraChatManagerDelegate` implements a callback to handle the receival of messages. When you receive the `messagesDidReceive` notification, you display the message to the user.
To handle these events, add the following extensions to the `ViewController`:
```swift
// Add message event callbacks
extension ViewController: AgoraChatManagerDelegate {
func messagesDidReceive(_ aMessages: [AgoraChatMessage]) {
for message in aMessages {
let msgBody = message.body as! AgoraChatTextMessageBody
DispatchQueue.main.async {
self.displayMessage(messageText: msgBody.text, isSentMessage: false)
}
showLog("Received a message from \(message.from)")
}
}
}
// Add connection event callbacks
extension ViewController: AgoraChatClientDelegate {
func connectionStateDidChange(_ aConnectionState: AgoraChatConnectionState) {
if aConnectionState == .connected {
showLog("Connected to the chat server.")
} else {
if isJoined {
showLog("Disconnected from the chat server.")
isJoined = false
}
}
}
func tokenWillExpire(_ aErrorCode: AgoraChatErrorCode) {
showLog("Token will expire (log in using new token)")
}
func tokenDidExpire(_ aErrorCode: AgoraChatErrorCode) {
showLog("Token expired (log in using new token)")
}
}
```
5. **Log in to the Chat**
When a user presses **Join**, your app logs in to the Chat. When a user presses **Leave**, the app logs out of the Chat.
To implement this logic, put the following method to the `ViewController`:
```swift
@objc func joinLeave() {
if (isJoined) {
let result: AgoraChatError? = agoraChatClient.logout(true)
guard result == nil else {
showLog(result!.errorDescription)
return
}
showLog("Signed out")
DispatchQueue.main.async {
self.btnJoinLeave.setTitle("Join", for: .normal)
self.isJoined = false
}
} else {
let result: AgoraChatError? = agoraChatClient.login(withUsername: userId, token: token)
guard result == nil else {
if (result!.code.rawValue == 200) { // Already joined
isJoined = true
showLog("Already signed in")
DispatchQueue.main.async {
self.btnJoinLeave.setTitle("Leave", for: .normal)
}
} else {
showLog(result!.errorDescription)
}
return
}
showLog("Signed in")
isJoined = true
DispatchQueue.main.async {
self.btnJoinLeave.setTitle("Leave", for: .normal)
}
}
}
```
6. **Send a message**
To send a message to the Chat when a user presses the **Send** button, perform the following steps:
1. Add the following method to the `ViewController` class before the `viewDidLoad` function:
```swift
@objc func sendMessage() {
// Read the recipient name from the EditText box
let toSendName = etRecipient.text!.trimmingCharacters(in: .whitespaces)
let content = etMessageText.text!.trimmingCharacters(in: .whitespaces)
if (toSendName.isEmpty || content.isEmpty) {
showLog("Enter a recipient name and a message.")
return
}
// Create a ChatMessage
let message = AgoraChatMessage(
conversationID: toSendName,
from: agoraChatClient.currentUsername!,
to: toSendName,
body: AgoraChatTextMessageBody(text: content),
ext: nil
)
// Send the message
agoraChatClient.chatManager?.send(message, progress: nil) { _, err in
if let err = err {
self.showLog("Error occurred when sending the message. \(err.errorDescription)")
} else {
self.showLog("Message sent successfully.")
DispatchQueue.main.async {
self.displayMessage(messageText: content, isSentMessage: true)
self.etMessageText.text = ""
}
}
}
}
```
7. **Display chat messages**
To display the messages the current user has sent and received in your app, add the following method to the `ViewController` class:
```swift
func displayMessage(messageText: String, isSentMessage: Bool) {
DispatchQueue.main.async {
self.scrollView.text.append(
DateFormatter.localizedString(from: Date.now, dateStyle: .none, timeStyle: .medium)
+ ": " + String(reflecting: messageText) + "\r\n"
)
self.scrollView.scrollRangeToVisible(NSRange(location: self.scrollView.text.count, length: 1))
}
}
```
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="web">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="web" />
Copy the following code to the `App.jsx` file to implement the UI:
```jsx
import { useEffect, useState, useRef } from "react";
import "./App.css";
import AgoraChat from "agora-chat";
function App() {
// Replaces with your app key.
const appKey = "";
const [userId, setUserId] = useState("");
const [token, setToken] = useState("");
const [isLoggedIn, setIsLoggedIn] = useState(false);
const [peerId, setPeerId] = useState("");
const [message, setMessage] = useState("");
const [logs, setLogs] = useState([]);
const chatClient = useRef(null);
// Logs into Agora Chat.
const handleLogin = () => {
if (userId && token) {
chatClient.current.open({
user: userId,
// Use agoraToken for v1.2.1 and earlier, but accessToken for 1.2.2 and later.
accessToken: token,
});
} else {
addLog("Please enter userId and token");
}
};
// Logs out.
const handleLogout = () => {
chatClient.current.close();
setIsLoggedIn(false);
setUserId("");
setToken("");
setPeerId("");
};
// Sends a peer-to-peer message.
const handleSendMessage = async () => {
if (message.trim()) {
try {
const options = {
chatType: "singleChat", // Sets the chat type as a one-to-one chat.
type: "txt", // Sets the message type.
to: peerId, // Sets the recipient of the message with user ID.
msg: message, // Sets the message content.
};
let msg = AgoraChat.message.create(options);
await chatClient.current.send(msg);
addLog(`Message send to ${peerId}: ${message}`);
setMessage("");
} catch (error) {
addLog(`Message send failed: ${error.message}`);
}
} else {
addLog("Please enter message content");
}
};
// Add log.
const addLog = (log) => {
setLogs((prevLogs) => [...prevLogs, log]);
};
useEffect(() => {
// Initializes the Web client.
chatClient.current = new AgoraChat.connection({
appKey: appKey,
});
// Adds the event handler.
chatClient.current.addEventHandler("connection&message", {
// Occurs when the app is connected to Agora Chat.
onConnected: () => {
setIsLoggedIn(true);
addLog(`User ${userId} Connect success !`);
},
// Occurs when the app is disconnected from Agora Chat.
onDisconnected: () => {
setIsLoggedIn(false);
addLog(`User Logout!`);
},
// Occurs when a text message is received.
onTextMessage: (message) => {
addLog(`${message.from}: ${message.msg}`);
},
// Occurs when the token is about to expire.
onTokenWillExpire: () => {
addLog("Token is about to expire");
},
// Occurs when the token has expired.
onTokenExpired: () => {
addLog("Token has expired");
},
onError: (error) => {
addLog(`on error: ${error.message}`);
},
});
}, []);
return (
<>
Agora Chat Examples
{!isLoggedIn ? (
<>
setUserId(e.target.value)}
placeholder="Enter the user ID"
/>
setToken(e.target.value)}
placeholder="Enter the token"
/>
Login
>
) : (
<>
Welcome, {userId}
Logout
setPeerId(e.target.value)}
placeholder="Enter the peer user ID"
/>
>
);
}
export default App;
```
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="flutter">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="flutter" />
### Handle the system logic [#handle-the-system-logic-2]
Import the Chat SDK package and set up the app framework.
1. **Add the Chat SDK package**
Open the file `/lib/main.dart` and add the following `import` statement at the top of the file:
```dart
import 'package:agora_chat_sdk/agora_chat_sdk.dart';
```
2. **Declare global variables**
In `/lib/main.dart` add the following declaration after the `import` statements:
```dart
// Global key to access the scaffold messenger
final GlobalKey scaffoldMessengerKey =
GlobalKey();
```
3. **Enable SnackBar messages**
Update the root widget to add the `scaffoldMessengerKey` for showing SnackBar messages, and set the background color. In `/lib/main.dart`, replace the `MyApp` class with the following:
```dart
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
scaffoldMessengerKey: scaffoldMessengerKey,
theme: ThemeData(
scaffoldBackgroundColor: const Color(0xFFECE5DD),
),
home: const MyHomePage(title: 'Agora Chat Quickstart'),
);
}
}
```
4. **Set up the app framework**
To set up a Flutter app framework for Chat, you do the following:
1. Create a stateful widget
2. Declare variables to manage the connection and access UI elements
3. Add a method to display information to the user
To do this, replace the `_MyHomePageState` class with the following:
```dart
class _MyHomePageState extends State {
static const String appKey = "";
static const String userId = "";
String token = "";
late ChatClient agoraChatClient;
bool isJoined = false;
ScrollController scrollController = ScrollController();
TextEditingController messageBoxController = TextEditingController();
String messageContent = "", recipientId = "";
final List messageList = [];
showLog(String message) {
scaffoldMessengerKey.currentState?.showSnackBar(SnackBar(
content: Text(message),
));
}
}
```
### Build the UI [#build-the-ui]
In the quickstart app, you create a simple UI that consists of the following elements:
* An `ElevatedButton` to log in or out of Agora Chat
* A `TextField` to specify the recipient user ID
* A `TextField` to enter a text message
* An `ElevatedButton` to send the text message
* A `ListView.builder` to display sent and received messages
To create this UI, add the following `build` method to the `_MyHomePageState` class:
```dart
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
padding: const EdgeInsets.all(10),
child: Column(
mainAxisSize: MainAxisSize.max,
children: [
Row(
children: [
Expanded(
child: SizedBox(
height: 40,
child: TextField(
decoration: const InputDecoration(
filled: true,
fillColor: Colors.white,
hintText: "Enter recipient's userId",
),
onChanged: (chatUserId) => recipientId = chatUserId,
),
),
),
const SizedBox(width: 10),
SizedBox(
width: 80,
height: 40,
child: ElevatedButton(
onPressed: joinLeave,
child: Text(isJoined ? "Leave" : "Join"),
),
),
],
),
const SizedBox(height: 10),
Expanded(
child: ListView.builder(
controller: scrollController,
itemBuilder: (_, index) {
return messageList[index];
},
itemCount: messageList.length,
),
),
Row(children: [
Expanded(
child: SizedBox(
height: 40,
child: TextField(
controller: messageBoxController,
decoration: const InputDecoration(
filled: true,
fillColor: Colors.white,
hintText: "Message",
),
onChanged: (msg) => messageContent = msg,
),
),
),
const SizedBox(width: 10),
SizedBox(
width: 50,
height: 40,
child: ElevatedButton(
onPressed: sendMessage,
child: const Text(">>"),
),
),
]),
],
),
),
);
}
```
### Send and receive messages [#send-and-receive-messages-2]
When a user opens the app, you instantiate and initialize a `ChatClient`. When the user taps the **Join** button, the app logs in to Agora Chat. When a user types a message in the text box and then presses **Send**, the typed message is sent to Agora Chat. When the app receives a message from the server, the message is displayed in the message list. This simple workflow enables you to rapidly build a Chat client with basic functionality.
The following figure shows the API call sequence for implementing this workflow.
API call sequence

To implement this workflow in your app, take the following steps:
1. **Set up Chat when the app starts**
When the app starts, you create an instance of the `ChatClient` and set up callbacks to handle Chat events.
To do this, add the following `initState` method to the `_MyHomePageState` class:
```dart
@override
void initState() {
super.initState();
setupChatClient();
setupListeners();
}
```
2. **Instantiate the `ChatClient`**
To implement peer-to-peer messaging, you use Chat SDK to initialize a `ChatClient` instance. In the `_MyHomePageState` class, add the following method after `initState`.
```dart
void setupChatClient() async {
ChatOptions options = ChatOptions(
appKey: appKey,
autoLogin: false,
);
agoraChatClient = ChatClient.getInstance;
await agoraChatClient.init(options);
// Notify the SDK that the Ul is ready. After the following method is executed, callbacks within ChatRoomEventHandler and ChatGroupEventHandler can be triggered.
await ChatClient.getlnstance.startCallback();
}
```
3. **Handle and respond to Chat events**
To receive notification of Chat events such as connection, disconnection, and token expiration, you add a `ConnectionEventHandler`. To handle message delivery and new message notifications, you add a `ChatEventHandler`. When you receive the `onMessageReceived` notification, you display the message to the user.
In the `_MyHomePageState` class, add the following methods after `setupChatClient`:
```dart
void setupListeners() {
agoraChatClient.addConnectionEventHandler(
"CONNECTION_HANDLER",
ConnectionEventHandler(
onConnected: onConnected,
onDisconnected: onDisconnected,
onTokenWillExpire: onTokenWillExpire,
onTokenDidExpire: onTokenDidExpire
),
);
agoraChatClient.chatManager.addEventHandler(
"MESSAGE_HANDLER",
ChatEventHandler(onMessagesReceived: onMessagesReceived),
);
}
void onMessagesReceived(List messages) {
for (var msg in messages) {
if (msg.body.type == MessageType.TXT) {
ChatTextMessageBody body = msg.body as ChatTextMessageBody;
displayMessage(body.content, false);
showLog("Message from ${msg.from}");
} else {
String msgType = msg.body.type.name;
showLog("Received $msgType message, from ${msg.from}");
}
}
}
void onTokenWillExpire() {
// The token is about to expire. Get a new token
// from the token server and renew the token.
}
void onTokenDidExpire() {
// The token has expired
}
void onDisconnected () {
// Disconnected from the Chat server
}
void onConnected() {
showLog("Connected");
}
```
4. **Log in to Agora Chat**
When a user presses **Join**, your app logs in to Agora Chat. When a user presses **Leave**, the app logs out of Agora Chat.
To implement this logic, in the `_MyHomePageState` class, add the following method before `showLog`:
```dart
void joinLeave() async {
if (!isJoined) { // Log in
try {
await agoraChatClient.loginWithToken(userId, token);
showLog("Logged in successfully as $userId");
setState(() {
isJoined = true;
});
} on ChatError catch (e) {
if (e.code == 200) { // Already logged in
setState(() {
isJoined = true;
});
} else {
showLog("Login failed, code: ${e.code}, desc: ${e.description}");
}
}
} else { // Log out
try {
await agoraChatClient.logout(true);
showLog("Logged out successfully");
setState(() {
isJoined = false;
});
} on ChatError catch (e) {
showLog("Logout failed, code: ${e.code}, desc: ${e.description}");
}
}
}
```
5. **Send a message**
To send a message to Agora Chat when a user presses the **Send** button, add the following method to the `_MyHomePageState` class, after `joinLeave`:
```dart
void sendMessage() async {
if (recipientId.isEmpty || messageContent.isEmpty) {
showLog("Enter recipient user ID and type a message");
return;
}
var msg = ChatMessage.createTxtSendMessage(
targetId: recipientId,
content: messageContent,
);
ChatClient.getInstance.chatManager.addMessageEvent(
"UNIQUE_HANDLER_ID",
ChatMessageEvent(
onSuccess: (msgId, msg) {
_addLogToConsole("on message succeed");
},
onProgress: (msgId, progress) {
_addLogToConsole("on message progress");
},
onError: (msgId, msg, error) {
_addLogToConsole(
"on message failed, code: ${error.code}, desc: ${error.description}",
);
},
),
);
ChatClient.getInstance.chatManager.removeMessageEvent("UNIQUE_HANDLER_ID");
agoraChatClient.chatManager.sendMessage(msg);
}
```
6. **Display chat messages**
To display the messages the current user has sent and received in your app, add the following method to the `_MyHomePageState` class:
```dart
void displayMessage(String text, bool isSentMessage) {
messageList.add(Row(children: [
Expanded(
child: Align(
alignment:
isSentMessage ? Alignment.centerRight : Alignment.centerLeft,
child: Container(
padding: const EdgeInsets.all(10),
margin: EdgeInsets.fromLTRB(
(isSentMessage ? 50 : 0), 5, (isSentMessage ? 0 : 50), 5),
decoration: BoxDecoration(
color: isSentMessage
? const Color(0xFFDCF8C6)
: const Color(0xFFFFFFFF),
),
child: Text(text),
),
),
),
]));
setState(() {
scrollController.jumpTo(scrollController.position.maxScrollExtent + 50);
});
}
```
7. **Remove event handlers on exit**
When a user closes the app, you remove the message and connection event handlers. To do this, add the following `dispose` method to the `_MyHomePageState` class:
```dart
@override
void dispose() {
agoraChatClient.chatManager.removeEventHandler("MESSAGE_HANDLER");
agoraChatClient.removeConnectionEventHandler("CONNECTION_HANDLER");
super.dispose();
}
```
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="react-native">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="react-native" />
## Implementation [#implementation]
This section introduces the codes you need to add to your project to start one-to-one messaging.
### Implement one-to-one messaging [#implement-one-to-one-messaging]
To send a one-to-one message, users must register a Chat account, log in to Agora Chat, and send a text message.
Open `simple_demo/App.js`, and replace the code with the following:
```javascript
// Imports dependencies.
SafeAreaView,
ScrollView,
StyleSheet,
Text,
TextInput,
View,
} from 'react-native';
ChatClient,
ChatOptions,
ChatMessageChatType,
ChatMessage,
} from 'react-native-agora-chat';
// Defines the App object.
const App = () => {
// Defines the variable.
const title = 'AgoraChatQuickstart';
// Replaces with your app key.
const appKey = '';
// Replaces with your user ID.
const [username, setUsername] = React.useState('');
// Replaces with your Agora token.
const [chatToken, setChatToken] = React.useState('');
const [targetId, setTargetId] = React.useState('');
const [content, setContent] = React.useState('');
const [logText, setWarnText] = React.useState('Show log area');
const chatClient = ChatClient.getInstance();
const chatManager = chatClient.chatManager;
// Outputs console logs.
useEffect(() => {
logText.split('\n').forEach((value, index, array) => {
if (index === 0) {
console.log(value);
}
});
}, [logText]);
// Outputs UI logs.
const rollLog = text => {
setWarnText(preLogText => {
let newLogText = text;
preLogText
.split('\n')
.filter((value, index, array) => {
if (index > 8) {
return false;
}
return true;
})
.forEach((value, index, array) => {
newLogText += '\n' + value;
});
return newLogText;
});
};
useEffect(() => {
// Registers listeners for messaging.
const setMessageListener = () => {
let msgListener = {
onMessagesReceived(messages) {
for (let index = 0; index < messages.length; index++) {
rollLog('received msgId: ' + messages[index].msgId);
}
},
onCmdMessagesReceived: messages => {},
onMessagesRead: messages => {},
onGroupMessageRead: groupMessageAcks => {},
onMessagesDelivered: messages => {},
onMessagesRecalled: messages => {},
onConversationsUpdate: () => {},
onConversationRead: (from, to) => {},
};
chatManager.removeAllMessageListener();
chatManager.addMessageListener(msgListener);
};
// Initializes the SDK.
// Initializes any interface before calling it.
const init = () => {
let o = new ChatOptions({
autoLogin: false,
appKey: appKey,
});
chatClient.removeAllConnectionListener();
chatClient
.init(o)
.then(() => {
rollLog('init success');
let listener = {
onTokenWillExpire() {
rollLog('token expire.');
},
onTokenDidExpire() {
rollLog('token did expire');
},
onConnected() {
rollLog('onConnected');
setMessageListener();
},
onDisconnected(errorCode) {
rollLog('onDisconnected:' + errorCode);
},
};
chatClient.addConnectionListener(listener);
})
.catch(error => {
rollLog(
'init fail: ' +
(error instanceof Object ? JSON.stringify(error) : error),
);
});
};
init();
}, [chatClient, chatManager, appKey]);
// Logs in with an account ID and a token.
const login = () => {
rollLog('start login ...');
chatClient
.loginWithToken(username, chatToken)
.then(() => {
rollLog('login operation success.');
})
.catch(reason => {
rollLog('login fail: ' + JSON.stringify(reason));
});
};
// Logs out from server.
const logout = () => {
rollLog('start logout ...');
chatClient
.logout()
.then(() => {
rollLog('logout success.');
})
.catch(reason => {
rollLog('logout fail:' + JSON.stringify(reason));
});
};
// Sends a text message to somebody.
const sendmsg = () => {
let msg = ChatMessage.createTextMessage(
targetId,
content,
ChatMessageChatType.PeerChat,
);
const callback = new (class {
onProgress(locaMsgId, progress) {
rollLog(`send message process: ${locaMsgId}, ${progress}`);
}
onError(locaMsgId, error) {
rollLog(`send message fail: ${locaMsgId}, ${JSON.stringify(error)}`);
}
onSuccess(message) {
rollLog('send message success: ' + message.localMsgId);
}
})();
rollLog('start send message ...');
chatClient.chatManager
.sendMessage(msg, callback)
.then(() => {
rollLog('send message: ' + msg.localMsgId);
})
.catch(reason => {
rollLog('send fail: ' + JSON.stringify(reason));
});
};
// Renders the UI.
return (
{title} setUsername(text)}
value={username}
autoCapitalize={'none'}
/>
setChatToken(text)}
value={chatToken}
autoCapitalize={'none'}
/>
SIGN IN
SIGN OUT
setTargetId(text)}
value={targetId}
autoCapitalize={'none'}
/>
setContent(text)}
value={content}
autoCapitalize={'none'}
/>
SEND TEXT
{logText}{}{}
);
};
// Sets UI styles.
const styles = StyleSheet.create({
titleContainer: {
height: 60,
backgroundColor: '#6200ED',
},
title: {
lineHeight: 60,
paddingLeft: 15,
color: '#fff',
fontSize: 20,
fontWeight: '700',
},
inputCon: {
marginLeft: '5%',
width: '90%',
height: 60,
paddingBottom: 6,
borderBottomWidth: 1,
borderBottomColor: '#ccc',
},
inputBox: {
marginTop: 15,
width: '100%',
fontSize: 14,
fontWeight: 'bold',
},
buttonCon: {
marginLeft: '2%',
width: '96%',
flexDirection: 'row',
marginTop: 20,
height: 26,
justifyContent: 'space-around',
alignItems: 'center',
},
eachBtn: {
height: 40,
width: '28%',
lineHeight: 40,
textAlign: 'center',
color: '#fff',
fontSize: 16,
backgroundColor: '#6200ED',
borderRadius: 5,
},
btn2: {
height: 40,
width: '45%',
lineHeight: 40,
textAlign: 'center',
color: '#fff',
fontSize: 16,
backgroundColor: '#6200ED',
borderRadius: 5,
},
logText: {
padding: 10,
marginTop: 10,
color: '#ccc',
fontSize: 14,
lineHeight: 20,
},
});
export default App;
```
### Build and run your project [#build-and-run-your-project]
To build and run the app on iOS platform:
```sh
yarn run ios
```
To build and run the app on Android platform:
```sh
yarn run android
```
To start the debug service:
```sh
yarn run start
```
If the project runs properly, the following user interface appears:
* Android platform

* iOS platform

<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="unity">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="unity" />
### Create the UI [#create-the-ui-2]
In the quick start app, you create a simple UI that consists of the following elements:
* A `Button - TextMeshPro` to log in or out of Agora Chat.
* An `Input Field - TextMeshPro` to specify the recipient user ID.
* An `Input Field - TextMeshPro` to enter a text message.
* A `Button - TextMeshPro` to send the text message.
* A `Scroll View` to display all sent and received messages.
To implement this user interface, in your Unity project:
1. **Set up the Scroll View**
1. In Unity Editor, right-click **Sample Scene**, then click **Game Object** > **UI** > **Scroll View**. A scroll view appears in the **Scene** Canvas. In **Inspector**, rename the **Scroll View** to `scrollView`.
If you can't see the view clearly in Unity, zoom out and rotate the scene
2. To adjust the `scrollView` size, change the following coordinates in **Inspector**:
* **Pos X** - `0`
* **Pos Y** - `0`
* **Width** - `250`
* **Height** - `300`
3. In **Hierarchy** select **scrollView > Viewport > Content**. In **Inspector** click **Add Component** and choose **Layout > Vertical Layout Group**.
4. In **Inspector** click **Add Component** again, and select **Layout > Content Size Fitter**. Set the **Vertical fit** property of the component to `Preferred Size`.
5. In **Inspector** click **Add Component** again, and select **UI > TextMeshPro - Text (UI)**. In the **TMP Importer** pop-up, click **Import TMP Essentials**. Your project imports the required components to your project. Expand the **TextMeshPro - Text** component properties and under **Extra Settings** set the **Left**, **Top**, **Right** and **Bottom** margins to `10`.
2. **Add a login button**
1. In **Hierarchy**, right-click **Sample Scene**, then click **UI** > **Button - TextMeshPro**. A button appears in the **Scene** Canvas. In **Inspector**, rename **Button** to `joinBtn`, then change the following properties:
* **Pos X** - `89`
* **Pos Y** - `160`
* **Width** - `70`
* **Height** - `30`
2. Select the **Text (TMP)** sub-item of `joinBtn` and in **Inspector**, change **Text** to `Join`.
3. **Add a send button**
1. Right-click **Sample Scene**, then click **UI** > **Button - TextMeshPro**. A button appears in the **Scene** Canvas. In **Inspector**, rename **Button** to `sendBtn`, then change the following properties:
* **Pos X** - `97`
* **Pos Y** - `-162`
* **Width** - `57`
* **Height** - `30`
2. Select the **Text (TMP)** sub-item of `sendBtn` and in **Inspector**, change **Text** to `>>`.
4. **Add input fields to enter a message and a recipient user ID**
1. Right-click **Sample Scene**, then click **UI** > **Input Field - TextMeshPro**. An input field appears in the **Scene** Canvas. In **Inspector**, rename **InputField (TMP)** to `message`, and change the following coordinates:
* **Pos X** - `-27`
* **Pos Y** - `-162`
* **Width** - `195`
* **Height** - `30`
2. Repeat the procedure in the previous step to add another input field named `userName`. In **Inspector** set the following coordinates:
* **Pos X** - `-35`
* **Pos Y** - `160`
* **Width** - `180`
* **Height** - `30`
### Handle the system logic [#handle-the-system-logic-3]
In this section, you create a script file, add the necessary namespaces, and bind your script to the canvas.
1. **Create a new script**
1. In **Project**, open **Assets** > **AgoraChat** > **Scripts**. Right-click **Scripts**, then click **Create** > **C# Script**. In **Assets**, you see the `NewBehaviourScript` file that you use to implement Chat SDK in your app.
2. In **Inspector**, click **Open**. `NewBehaviourScript.cs` opens in your default text editor.
2. **Import the relevant Agora and Unity classes**
In `NewBehaviourScript.cs`, add the following lines after `using UnityEngine;`:
```csharp
using UnityEngine.UI;
using TMPro;
using AgoraChat;
using AgoraChat.MessageBody;
```
3. **Bind your script to the canvas**
1. Drag `NewBehaviourScript.cs` from `Assets/AgoraChat/Scripts`, and drop it on to **Canvas** in the **Hierarchy**.
2. Click **Canvas**, you see that your script is added to **Canvas** in **Inspector**.
### Send and receive messages [#send-and-receive-messages-3]
When a user opens the app, you instantiate and initialize a `SDKClient`. When the user taps the **Join** button, the app logs in to Agora Chat. When a user types a message in the text box and then presses **>>**, the typed message is sent to Agora Chat. When the app receives a message from the server, the message is displayed in the message list. This simple workflow enables you to rapidly build a Chat client with basic functionality.
The following figure shows the API call sequence for implementing this workflow.
API call sequence

To implement this workflow in your app, take the following steps:
1. **Declare variables**
In the `NewBehaviourScript` class, add the following declarations:
```csharp
private TMP_Text messageList;
private string userId = "";
private string token = "";
private string appKey = "";
private bool isJoined = false;
SDKClient agoraChatClient;
```
2. **Set up Chat when the app starts**
When the app starts, you set up a `SDKClient` and callbacks to handle Chat events. To do this, replace the `Start` method in the `NewBehaviourScript` class with the following:
```csharp
void Start()
{
GameObject.Find("userName/Text Area/Placeholder").GetComponent().text = "Enter recipient name";
GameObject.Find("message/Text Area/Placeholder").GetComponent().text = "Message";
messageList = GameObject.Find("scrollView/Viewport/Content").GetComponent();
messageList.fontSize = 14;
messageList.text = "";
GameObject button = GameObject.Find("joinBtn");
button.GetComponent().onClick.AddListener(joinLeave);
button = GameObject.Find("sendBtn");
button.GetComponent().onClick.AddListener(sendMessage);
setupChatSDK();
}
```
3. **Instantiate the `SDKClient`**
To implement peer-to-peer messaging, you use Chat SDK to initialize a `SDKClient` instance. In the `NewBehaviourScript` class, add the following method before `Start`.
```csharp
void setupChatSDK()
{
if (appKey == "")
{
Debug.Log("You should set your appKey first!");
return;
}
// Initialize the Agora Chat SDK
Options options = new Options(appKey);
options.UsingHttpsOnly = true;
options.DebugMode = true;
agoraChatClient = SDKClient.Instance;
agoraChatClient.InitWithOptions(options);
}
```
4. **Handle and respond to Chat events**
To receive notification of Chat events such as connection, disconnection, and token expiration, you use `IChatManagerDelegate` and `IConnectionDelegate`. When you receive the `onMessageReceived` notification, you display the message to the user. To implement these notifications in your app, do the following:
1. In the `NewBehaviourScript` class, add the following at the end of `setupChatSDK`:
```csharp
agoraChatClient.ChatManager.AddChatManagerDelegate(this);
```
2. Implement the `IChatManagerDelegate` and `IConnectionDelegate` interfaces in the `NewBehaviourScript` class. In `NewBehaviourScript.cs`, add the following after `public class NewBehaviourScript : MonoBehaviour`:
```csharp
, IChatManagerDelegate, IConnectionDelegate
```
3. In the `NewBehaviourScript` class, add the following callbacks before `setupChatSDK`:
```csharp
public void OnMessagesReceived(List messages)
{
foreach (Message msg in messages)
{
if (msg.Body.Type == MessageBodyType.TXT)
{
TextBody txtBody = msg.Body as TextBody;
string Msg = msg.From + ":" + txtBody.Text;
displayMessage(Msg, false);
}
}
}
public void OnCmdMessagesReceived(List messages)
{
// This callback is triggered only by the reception of a command message that is usually invisible to users.
}
public void OnMessagesRead(List messages)
{
// Occurs when a read receipt is received for a message.
}
public void OnMessagesDelivered(List messages)
{
// Occurs when a delivery receipt is received.
}
public void OnMessagesRecalled(List messages)
{
// Occurs when a received message is recalled.
}
public void OnReadAckForGroupMessageUpdated()
{
// Occurs when the read status updates of a group message is received.
}
public void OnGroupMessageRead(List list)
{
// Occurs when a read receipt is received for a group message.
}
public void OnConversationsUpdate()
{
// Occurs when the number of conversations changes.
}
public void OnConversationRead(string from, string to)
{
// Occurs when the read receipt is received for a conversation.
}
public void MessageReactionDidChange(List list)
{
// Occurs when the reactions change.
}
public void OnConnected()
{
Debug.Log("Connected");
}
public void OnTokenExpired()
{
// Occurs when the token has expired.
}
public void OnTokenWillExpire()
{
// Occurs when the token is about to expire
}
public void OnAuthFailed()
{
// Occurs when the user is forced to log out of the current account due to an authentication failure.
}
public void OnKickedByOtherDevice()
{
// Occurs when the user is forced to log out of the current account on the current device due to login to another device.
}
public void OnLoginTooManyDevice()
{
// Occurs when the user is forced to log out of the current account because he or she reached the maximum number of devices that the user can log in to with the current account.
}
public void OnChangedIMPwd()
{
// Occurs when the user is forced to log out of the current account because the login password has changed.
}
public void OnForbidByServer()
{
// Occurs when the current user account is banned.
}
public void OnRemovedFromServer()
{
// Occurs when the current user account is removed from the server.
}
public void OnLoggedOtherDevice(string deviceName, string info)
{
// Occurs when the user logs in to another device with the current account.
}
public void OnDisconnected()
{
Debug.Log("Disconnected");
}
public void OnMessageContentChanged(Message msg, string operatorId, long operationTime)
{
}
public void OnMessagePinChanged(string messageId, string conversationId, bool isPinned, string operatorId, long operationTime)
{
}
public void OnLoggedOtherDevice(string str)
{
}
public void OnAppActiveNumberReachLimitation()
{
}
public void OnMessageContentChanged(Message msg, string operatorId, long operationTime)
{
}
public void OnMessagePinChanged(string messageId, string conversationId, bool isPinned, string operatorId, long operationTime)
{
}
public void OnOfflineMessageSyncStart()
{
}
public void OnOfflineMessageSyncFinish()
{
}
```
5. **Log in to Agora Chat**
When a user presses **Join**, your app logs in to Agora Chat. When a user presses **Leave**, the app logs out of Agora Chat. To implement this logic, in the `NewBehaviourScript` class, add the following method before `Start`:
```csharp
public void joinLeave()
{
if (isJoined)
{
agoraChatClient.Logout(true, callback: new CallBack(
onSuccess: () =>
{
Debug.Log("Logout succeed");
isJoined = false;
GameObject.Find("joinBtn").GetComponentInChildren().text = "Join";
},
onError: (code, desc) =>
{
Debug.Log($"Logout failed, code: {code}, desc: {desc}");
}));
}
else
{
// Use LoginWithToken to replace LoginWithAgoraToken for the SDK of v1.3.0 or later
agoraChatClient.LoginWithAgoraToken(userId, token, callback: new CallBack(
onSuccess: () =>
{
Debug.Log("Login succeed");
isJoined = true;
GameObject.Find("joinBtn").GetComponentInChildren().text = "Leave";
},
onError: (code, desc) =>
{
Debug.Log($"Login failed, code: {code}, desc: {desc}");
}));
}
}
```
6. **Send a message**
To send a message to Agora Chat when a user presses the **>>** button, add the following method to the `NewBehaviourScript` class, before `Start`:
```csharp
public void sendMessage()
{
string recipient = GameObject.Find("userName").GetComponent().text;
string Msg = GameObject.Find("message").GetComponent().text;
if (Msg == "" || recipient == "")
{
Debug.Log("You did not type your message");
return;
}
Message msg = Message.CreateTextSendMessage(recipient, Msg);
displayMessage(Msg, true);
agoraChatClient.ChatManager.SendMessage(ref msg, new CallBack(
onSuccess: () =>
{
Debug.Log($"Send message succeed");
GameObject.Find("message").GetComponent().text = "";
},
onError: (code, desc) =>
{
Debug.Log($"Send message failed, code: {code}, desc: {desc}");
}));
}
```
7. **Display chat messages**
To display sent and received messages in your app, add the following method to the `NewBehaviourScript` class:
```csharp
public void displayMessage(string messageText, bool isSentMessage)
{
if (isSentMessage)
{
messageList.text += "" + messageText + "\n";
}
else
{
messageList.text += "" + messageText + "\n";
}
}
```
8. **Clean up the resources**
When you quit the app, it calls `OnApplicationQuit`. You use this method for clean up. To Implement the clean up logic, in the `NewBehaviourScript` class, add the following after `Start`:
```csharp
void OnApplicationQuit()
{
agoraChatClient.ChatManager.RemoveChatManagerDelegate(this);
agoraChatClient.Logout(true, callback: new CallBack(
onSuccess: () =>
{
Debug.Log("Logout succeed");
},
onError: (code, desc) =>
{
Debug.Log($"Logout failed, code: {code}, desc: {desc}");
}));
}
```
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="windows">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="windows" />
### Create the UI [#create-the-ui-3]
In your project, create a simple UI that consists of the following elements:
* A `Button` to **Join** or **Leave** Agora Chat.
* A `TextBox` box to specify the recipient user ID.
* A `TextBox` box to enter a text message.
* A `Button` to send the text message.
* A scrollable layout to display sent and received messages.
To add the UI framework to your device project, In Solution Explorer, open `` **>** `MainWindow.xaml`, then replace the file contents with the following:
```xml
```
### Handle the system logic [#handle-the-system-logic-4]
This section shows you how to add Chat SDK headers and setup logging. To do this:
1. **Import the Agora namespaces**
In the Solution Explorer, select `` > `MainWindow.xaml`, and double-click it to open the `MainWindow.xaml.cs` file. Add the following lines to the namespaces list in the file:
```c#
using AgoraChat;
using AgoraChat.MessageBody;
```
2. **Log events and show status updates to your users**
In the `MainWindow` class, add the following helper method:
```c#
private void showLog(string log)
{
// Show a message box
Dip?.Invoke(() =>
{
MessageBox.Show(log);
});
// Write log
Console.WriteLine(log);
}
```
### Send and receive messages [#send-and-receive-messages-4]
When a user opens the app, you instantiate and initialize a `SDKClient`. When the user clicks the **Join** button, the app logs in to Agora Chat. When a user types a message in the text box and then presses Send, the typed message is sent to another registered user. When the app receives a message from Agora Chat, the message is displayed in the message list. This simple workflow enables you to rapidly build a Chat client with basic functionality.
The following figure shows the API call sequence for implementing this workflow.
API call sequence

To implement this workflow in your app, take the following steps:
1. **Declare connection variables**
In the `MainWindow` class, add the following declarations:
```c#
private readonly string userId = "";
private string token = "";
private readonly string app_key = "";
private bool isJoined = false;
// Update the UI from callbacks on a separate thread
private readonly System.Windows.Threading.Dispatcher? Dip = null;
```
2. **Set up Chat when the app starts**
When the app starts, you create an instance of `SDKClient` and set up delegates to handle connection and Chat events.
To do this, replace the `MainWindow()` constructor in the `MainWindow` class with the following:
```c#
public MainWindow()
{
// Initialize the thread dispatcher
Dip = System.Windows.Threading.Dispatcher.CurrentDispatcher;
// Initialize Chat SDK client
setupChatClient();
// Add connection and chat delegates to handle callbacks
SDKClient.Instance.AddConnectionDelegate(this);
SDKClient.Instance.ChatManager.AddChatManagerDelegate(this);
// Execute the CloseWindow method when the app closes
Closed += CloseWindow;
}
```
3. **Initialize the `SDKClient`**
To implement peer-to-peer messaging, you use Chat SDK to initialize a `SDKClient` instance. In the `MainWindow` class, add the following method:
```c#
private void setupChatClient()
{
var options = new Options(appKey: app_key);
options.UsingHttpsOnly = true;
// Initialize the chat SDKClient
SDKClient.Instance.InitWithOptions(options);
}
```
4. **Log in to Agora Chat**
When a user clicks **Join**, your app logs in to Agora Chat. When they click **Leave**, the app logs out. To implement this logic, add the following method after `showLog()` in the `MainWindow` class:
```c#
private void JoinLeave_Click(object sender, RoutedEventArgs e)
{
Button button = (Button)this.FindName("btnJoinLeave");
if (isJoined)
{
SDKClient.Instance.Logout(true, callback: new CallBack(
onSuccess: () =>
{
showLog("Sign out from Agora Chat succeed");
Dip?.Invoke(() =>
{
button.Content = "Join";
});
isJoined = false;
},
onError: (code, desc) =>
{
showLog(desc);
}
));
}
else
{
// Log in to the Chat service with userId and Agora token.
SDKClient.Instance.LoginWithToken(userId, token, callback: new CallBack(
onSuccess: () =>
{
showLog("Sign in to Agora Chat succeed");
isJoined = false;
Dip?.Invoke(() =>
{
button.Content = "Leave";
});
},
onError: (code, desc) =>
{
if (code == 200)
{ // Already joined
isJoined = true;
Dip?.Invoke(() =>
{
button.Content = "Leave";
});
}
else
{
showLog(desc);
}
}
));
}
}
```
5. **Send a message**
When a user types a message and presses the Send button, the app sends the message to the recipient. To do this, add the following method to the `MainWindow` class, after `JoinLeave_Click()`:
```c#
private void MessageSend_Click(object sender, RoutedEventArgs e)
{
if (eReciepientUserId.Text.Equals("") || eMessage.Text.Equals(""))
{
showLog("Enter a recipient name and a message");
return;
}
//Send Message
Message msg = Message.CreateTextSendMessage(eReciepientUserId.Text, eMessage.Text);
SDKClient.Instance.ChatManager.SendMessage(ref msg, new CallBack(
onSuccess: () =>
{
// The success callback uses the System.Windows.Threading.Dispatcher to update UI elements
Dip?.Invoke(() =>
{
TextBody? txtBody = msg.Body as TextBody;
if (txtBody != null)
{
displayMessage(txtBody.Text, true);
}
eMessage.Text = "";
});
},
onError: (code, desc) =>
{
showLog(desc);
}
));
}
```
6. **Set up callbacks for handling Chat events**
To set up callbacks for handling connection and Chat events, you implement the `IConnectionDelegate` and `IChatManagerDelegate` interfaces in the `MainWindow` class. Update the `MainWindow` class definition as follows:
```c#
public partial class MainWindow : Window, IConnectionDelegate, IChatManagerDelegate
```
7. **Handle connection events**
To implement all `IConnectionDelegate` callbacks, add the following code to the `MainWindow` class:
```c#
public void OnConnected()
{
showLog("SDK connected successfully.");
}
public void OnDisconnected()
{
showLog("SDK disconnected.");
}
public void OnLoggedOtherDevice(string deviceName, string info)
{
}
public void OnRemovedFromServer()
{
}
public void OnForbidByServer()
{
}
public void OnChangedIMPwd()
{
}
public void OnLoginTooManyDevice()
{
}
public void OnKickedByOtherDevice()
{
}
public void OnAuthFailed()
{
showLog("User authentication failed.");
}
public void OnTokenExpired()
{
showLog("The token has expired.");
}
public void OnTokenWillExpire()
{
showLog("The token is about to expire. Get a new token from the token server and renew the token.");
}
public void OnAppActiveNumberReachLimitation()
{
}
public void OnOfflineMessageSyncStart()
{
}
public void OnOfflineMessageSyncFinish()
{
}
```
8. **Handle Chat events**
To read and display received chat messages, you add code to the `OnMessagesReceived` callback of the `IChatManagerDelegate`. To implement all `IChatManagerDelegate` callbacks, add the following code to the `MainWindow` class:
```c#
public void OnMessagesReceived(List messages)
{
foreach (Message msg in messages)
{
Dip?.Invoke(() =>
{
TextBody? txtBody = msg.Body as TextBody;
if (txtBody != null)
{
displayMessage(txtBody.Text, false);
}
});
}
}
public void OnCmdMessagesReceived(List messages)
{
}
public void OnMessagesRead(List messages)
{
}
public void OnMessagesDelivered(List messages)
{
}
public void OnMessagesRecalled(List messages)
{
}
public void OnReadAckForGroupMessageUpdated()
{
}
public void OnGroupMessageRead(List list)
{
}
public void OnConversationsUpdate()
{
}
public void OnConversationRead(string from, string to)
{
}
public void MessageReactionDidChange(List list)
{
}
public void OnMessageContentChanged(Message msg, string operatorId, long operationTime)
{
}
public void OnMessagePinChanged(string messageId, string conversationId, bool isPinned, string operatorId, long operationTime)
{
}
```
9. **Display chat messages**
To display sent and received messages, add the following method to the `MainWindow` class:
```c#
private void displayMessage(string messageText, bool isSentMessage)
{
// Create a new TextBlock
TextBlock messageTextBlock = new TextBlock();
messageTextBlock.Text = messageText;
messageTextBlock.Padding = new Thickness(10);
// Set formatting
ScrollViewer? messageList = FindName("scrollMessagesView") as ScrollViewer;
if (messageList != null)
{
if (isSentMessage)
{
messageTextBlock.Background = new SolidColorBrush(Color.FromRgb(220, 248, 198));
messageTextBlock.HorizontalAlignment = HorizontalAlignment.Right;
messageTextBlock.Margin = new Thickness(100, 25, 15, 5);
}
else
{
messageTextBlock.Background = Brushes.White;
messageTextBlock.HorizontalAlignment = HorizontalAlignment.Left;
messageTextBlock.Margin = new Thickness(15, 25, 100, 5);
}
// Add the message TextBlock to the StackPanel inside the ScrollViewer
StackPanel? messageStackPanel = messageList.Content as StackPanel;
if (messageStackPanel != null)
{
messageStackPanel.Children.Add(messageTextBlock);
messageList.ScrollToEnd(); // Scroll to the bottom of the messages
}
}
}
```
10. **Clean up when the window is closed**
Add the following method to the `MainWindow` class:
```c#
private void CloseWindow(object sender, EventArgs e)
{
SDKClient.Instance.ChatManager.RemoveChatManagerDelegate(this);
SDKClient.Instance.DeleteConnectionDelegate(this);
}
```
<_PlatformProcessedMarker close="true" />
## Test your implementation [#test-your-implementation]
To ensure that you have implemented Peer-to-Peer Messaging in your app:
<_PlatformTabsGroup groupMode="structured" canonicalPlatform="web" platforms="["android","ios","web","flutter","react-native","unity","windows"]" showTabs="true">
<_PlatformPanel platform="android">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="android" />
1. Create an app instance for the first user:
1. [Register a user](./enable#register-a-user) in [Agora Console](https://console.agora.io/v2) and [Generate a user token](./enable#generate-a-user-token).
2. In the `MainActivity` class, update `userId`, `token`, and `appKey` with values from Agora Console. To get your app key, see [Get Chat project information](./enable#get-chat-project-information).
3. Connect a physical Android device to your development device.
4. In Android Studio, click **Run app**. A moment later you see the project installed on your device.
2. Create an app instance for the second user:
1. Register a second user in Agora Console and generate a user token.
2. In the `MainActivity` class, update `userId` and `token` with values for the second user. Make sure you use the same `appKey` as for the first user.
3. Run the modified app on a device emulator or a second physical Android device.
3. On each device, click **Join** to log in to Agora Chat.
4. Edit the recipient name on each device to show the user ID of the user logged in to the other device.
5. Type a message in the **Message** box of either device and press **`>>`**.
The message is sent and appears on the other device.
6. Press **Leave** to log out of Agora Chat.
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="ios">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="ios" />
1. Create an app instance for the first user:
1. [Register a user](./enable#register-a-user) in [Agora Console](https://console.agora.io/v2) and [Generate a user token](./enable#generate-a-user-token).
2. In the `ViewController` class, update `userId`, `token`, and `appKey` with values from Agora Console. To get your app key, see [Get Chat project information](./enable#get-chat-project-information).
3. Connect a physical device to your development device.
4. In Xcode, click **Run app**. A moment later you see the project installed on your device.
2. Create an app instance for the second user:
1. Register a second user in Agora Console and generate a user token.
2. In the `ViewController` class, update `userId` and `token` with values for the second user. Make sure you use the same `appKey` as for the first user.
3. Run the modified app on a device emulator or a second physical device.
3. On each device, click **Join** to log in to the Chat.
4. Edit the recipient name on each device to show the user ID of the user logged in to the other device.
5. Type a message in the **Message** box of either device and press **`>>`**.
The message is sent and appears on the other device.
6. Press **Leave** to log out of the Chat.
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="web">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="web" />
Use vite to build the project. You can run below commands to run the project.
```bash
$ npm install
```
```bash
$ npm run dev
```
The following page opens in your browser:

To validate the peer-to-peer messaging you have just integrated into your Web app using Agora Chat:
1. Log in
Fill in the user ID of the sender (`Leo`) in the **user id** box and agora token in the **token** box, and click **Login** to log in to the app.
2. Send a message
Fill in the user ID of the receiver (`Roy`) in the **single chat id** box and type in the message ("Hi, how are you doing?") to send in the **message content** box, and click **Send** to send the message.

3. Log out
Click **Logout** to log out of the app.
4. Receive the message
Open the same page in a new window, log in as the receiver (`Roy`) and receive the message ("Hi, how are you doing?") sent from **Leo**.

<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="flutter">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="flutter" />
1. Create an app instance for the first user:
1. [Register a user](./enable#register-a-user) in [Agora Console](https://console.agora.io/v2) and [Generate a user token](./enable#generate-a-user-token).
2. In the `_MyHomePageState` class, update `appKey`, `token`, and `userId` with values from Agora Console. To get your app key, see [Get Chat project information](./enable#get-chat-project-information).
3. Connect a physical test device to your development device.
4. In your IDE, click **Run app** or launch your app from the terminal using `flutter run lib/main.dart`. A moment later you see the project installed on your device.
2. Create an app instance for the second user:
1. Register a second user in Agora Console and generate a user token.
2. In the `_MyHomePageState` class, update `userId` and `token` with values for the second user. Make sure you use the same `appKey` as for the first user.
3. Run the modified app on a device emulator or a second physical test device.
3. On each device, click **Join** to log in to Agora Chat.
4. Edit the recipient name on each device to show the user ID of the user logged in to the other device.
5. Type a message in the **Message** box of either device and press **`>>`**.
The message is sent and appears on the other device.
6. Press **Leave** to log out of Agora Chat.
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="react-native">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="react-native" />
You can log in to the app by either modifying the fields in the App.js file as stated below, or entering the required fields in the user interface.
To validate the peer-to-peer messaging you have just integrated into your app using Agora Chat, perform the following operations:
1. Log in
a. In the [`App.js`](#sign-in) file, replace the placeholders of `appKey`, `username`, and `chatToken` with the App Key, user ID, and Agora token of the sender.
b. Run and [build](#build) your app.
c. On your simulator or physical device, click **SIGN IN** to log in with the sender account.
2. Send a message
Fill in the user ID of the receiver in the **Enter the username you want to send** box, type in te message to send in the **Enter content** box, and click **SEND TEXT** to send the message.
3. Log out
Click **SIGN OUT** to log out of the sender account.
4. Receive the message
a. After signing out, change the values of `appKey`, `username`, and `chatToken` to the user ID, Agora token, and App Key of the receiver in the [`App.js`](#sign-in) file.
b. Run the app on another device or simulator with the receiver account and receive the message sent in step 2.
You can also read from the logs on the UI to see whether you have successfully signed in, signed out, and sent and received a text message.
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="unity">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="unity" />
1. [Register a user](./enable#register-a-user) in [Agora Console](https://console.agora.io/v2) and [Generate a user token](./enable#generate-a-user-token).
2. In the `NewBehaviourScript` class, update `userId`, `token`, and `appKey` with values from Agora Console. To get your app key, see [Get Chat project information](./enable#get-chat-project-information).
3. In Unity Editor, click **Play**. A moment later you see the project installed on your development device.
4. Click **Join** to log in to Agora Chat.
5. Type the recipient user name in the **Recepient Name** field.
6. Type a message in the **Message** field and press **`>>`**.
The message is sent and a confirmation message is displayed in the debug console.
7. [Register the recipient user](./enable#register-a-user) in Agora Console and [Generate a user token](./enable#generate-a-user-token).
8. In the `NewBehaviourScript` class, update `userId` and `token` with values you generated for recipient. Make sure you use the same `appKey` as for both users.
9. Save your changes and click **Play** to run the app.
10. Click **Join** to log in to the Chat.
You see a message in the scroll view that you sent earlier.
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="windows">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="windows" />
1. Create an app instance for the first user:
1. [Register a user](./enable#register-a-user) in [Agora Console](https://console.agora.io/v2) and [Generate a user token](./enable#generate-a-user-token).
2. In the `MainWindow` class, update `userId`, `token`, and `appKey` with values from Agora Console. To get your app key, see [Get Chat project information](./enable#get-chat-project-information).
3. Click the **Start** button. You see the following interface:

2. Create an app instance for the second user:
1. Register a second user in Agora Console and generate a user token.
2. Build a second instance of your app after updating `userId` and `token` with values for the second user. Make sure you use the same `appKey` as for the first user.
3. Click the **Start** button to launch the second instance.
3. On each instance, click **Join** to log in to Agora Chat.
4. Edit the recipient name in each app instance to show the user ID of the user logged in to the other instance.
5. Type a message in the **Message** box of either instance and press **`>>`**. The message is sent and appears on the other app.

6. Press **Leave** to log out from Agora Chat.
<_PlatformProcessedMarker close="true" />
## Reference [#reference]
This section contains content that completes the information in this page, or points you to documentation that explains other aspects to this product.
* For more code samples, see [Samples and demos](./downloads).
<_PlatformTabsGroup groupMode="structured" canonicalPlatform="web" platforms="["android","ios","web","flutter","react-native","unity","windows"]" showTabs="true">
<_PlatformPanel platform="android">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="android" />
* [Manual install](./reference/manual-sdk-install) shows you how to integrate Chat SDK into your project manually.
### Integration issues [#integration-issues]
If your project integrates Agora Chat SDK 1.3.2 or later and either Signaling SDK 2.2.0 or later or Video SDK 4.3.0 or later, you may encounter a compilation error because the `libaosl.so` library is included in both SDKs.
```
com.android.builder.merge.DuplicateRelativeFileException: More than one file was found with OS independent path 'lib/x86/libaosl.so'
```
To resolve this issue, add packaging options under the `Android` node in the `build.gradle` file of your app. This ensures that the build process prioritizes the first matching file:
Groovy
Kotlin DSL
* **Android Gradle Plugin \< 7.x**
```text
android {
packagingOptions {
pickFirst 'lib/**/libaosl.so'
}
}
dependencies {
implementation 'io.agora.infra:aosl:x.y.z'
// implementation RTM sdk
// implementation RTC sdk
}
```
* **Android Gradle Plugin 7.x or later**
```text
android {
packaging {
jniLibs {
pickFirsts += ["lib/**/libaosl.so"]
}
}
}
dependencies {
implementation 'io.agora.infra:aosl:x.y.z'
// implementation RTM sdk
// implementation RTC sdk
}
```
* **Android Gradle Plugin \< 7.x**
```kotlin
android {
packagingOptions {
pickFirst("lib/**/libaosl.so")
}
}
dependencies {
implementation("io.agora.infra:aosl:x.y.z")
// implementation RTM sdk
// implementation RTC sdk
}
```
* **Android Gradle Plugin 7.x or later**
```kotlin
android {
packaging {
jniLibs {
pickFirsts += setOf("lib/**/libaosl.so")
}
}
}
dependencies {
implementation("io.agora.infra:aosl:x.y.z")
// implementation RTM sdk
// implementation RTC sdk
}
```
### API reference [#api-reference]
* [`ChatClient.init()`](https://api-ref.agora.io/en/chat-sdk/android/1.x/classio_1_1agora_1_1chat_1_1_chat_client.html#ab8220f870c05ad326f4de256c5814852)
* [`ChatClient.loginWithToken()`](https://api-ref.agora.io/en/chat-sdk/android/1.x/classio_1_1agora_1_1chat_1_1_chat_client.html#abb72e1e403e7e3f4ded23e8b4f460bd6)
* [`ChatClient.logout()`](https://api-ref.agora.io/en/chat-sdk/android/1.x/classio_1_1agora_1_1chat_1_1_chat_client.html#a014e2abb85595417b64799dabfb8ac74)
* [`ChatManager.sendMessage()`](https://api-ref.agora.io/en/chat-sdk/android/1.x/classio_1_1agora_1_1chat_1_1_chat_manager.html#aed75ce0a590423ac18a94e1d339e97f4)
* [`ChatManager`](https://api-ref.agora.io/en/chat-sdk/android/1.x/classio_1_1agora_1_1chat_1_1_chat_manager.html)
* [`ChatMessage`](https://api-ref.agora.io/en/chat-sdk/android/1.x/classio_1_1agora_1_1chat_1_1_chat_message.html)
* [`ConnectionListener`](https://api-ref.agora.io/en/chat-sdk/android/1.x/interfaceio_1_1agora_1_1_connection_listener.html)
* [`MessageListener`](https://api-ref.agora.io/en/chat-sdk/android/1.x/interfaceio_1_1agora_1_1_message_listener.html)
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="ios">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="ios" />
### Integrate the SDK through CocoaPods [#integrate-the-sdk-through-cocoapods]
1. Install CocoaPods. For details, see [Getting Started with CocoaPods](https://guides.cocoapods.org/using/getting-started.html#getting-started).
2. In the Terminal, navigate to the project root directory and run the `pod init` command to create a text file `Podfile` in the project folder.
3. Open the `Podfile` file and add the Agora Chat SDK. Remember to replace `Your project target` with the target name of your project.
```swift
platform :ios, '11.0'
target 'Your project target' do
pod 'Agora_Chat_iOS'
end
```
4. In the project root directory, run the following command to integrate the SDK:
```swift
pod install
```
When the SDK is installed successfully, you can see `Pod installation complete!` in the Terminal and an `xcworkspace` file in the project folder.
5. Open the `xcworkspace` file in Xcode.
### Add a privacy manifest file [#add-a-privacy-manifest-file]
The Agora Chat SDK for device 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 `PrivacyInfo.xcprivacy` file of the Agora Chat SDK to `PrivacyInfo.xcprivacy` of the app in either of the following ways:
* Add the following source code:
```xml
NSPrivacyAccessedAPITypesNSPrivacyAccessedAPITypeReasonsCA92.1NSPrivacyAccessedAPITypeNSPrivacyAccessedAPICategoryUserDefaultsNSPrivacyAccessedAPITypeReasonsC617.1NSPrivacyAccessedAPITypeNSPrivacyAccessedAPICategoryFileTimestampNSPrivacyCollectedDataTypesNSPrivacyTrackingDomainsNSPrivacyTracking
```
* Add the list of items as shown in the following figure:

### Frequently asked questions [#frequently-asked-questions]
* [How can I add a privacy manifest to my iOS app?](/en/api-reference/faq/other/ios_privacy_manifest)
### API reference [#api-reference-1]
* [`AgoraChatClient.initializeSDKWithOptions`](https://api-ref.agora.io/en/chat-sdk/ios/1.x/interface_agora_chat_client.html#aa628257db8692884cc69e39cc6d7a58b)
* [`AgoraChatClient.loginWithUsername:token:`](https://api-ref.agora.io/en/chat-sdk/ios/1.x/interface_agora_chat_client.html#a504a9bef378815cb0da3f35a559db4a8)
* [`AgoraChatClient.logout`](https://api-ref.agora.io/en/chat-sdk/ios/1.x/interface_agora_chat_client.html#a71bb810fe29f67741fe820811370c809)
* [`AgoraChatManager.sendMessage:progress:completion:`](https://api-ref.agora.io/en/chat-sdk/ios/1.x/protocol_i_agora_chat_manager-p.html#a78bad292be9cd44c08df18e557b9939a)
* [`AgoraChatManagerDelegate`](https://api-ref.agora.io/en/chat-sdk/ios/1.x/protocol_agora_chat_manager_delegate-p.html)
* [`AgoraChatClientDelegate`](https://api-ref.agora.io/en/chat-sdk/ios/1.x/protocol_agora_chat_client_delegate-p.html)
* [`AgoraChatOptions`](https://api-ref.agora.io/en/chat-sdk/ios/1.x/interface_agora_chat_options.html)
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="web">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="web" />
For details:
* [Manual install](./reference/manual-sdk-install) shows you how to integrate Chat SDK into your project manually.
* [Sample code](https://github.com/AgoraIO/Agora-Chat-API-Examples/blob/main/Chat-Web/src/index.js) for getting
started with Chat.
* Install the [demo app](./reference/downloads).
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="flutter">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="flutter" />
### API reference [#api-reference-2]
* [`ChatClient.init`](https://api-ref.agora.io/en/chat-sdk/flutter/1.x/file-___Users_dujiepeng_work_flutter_agora_chat_sdk_lib_src_chat_client/ChatClient/init.html)
* [`ChatClient.loginWithToken`](https://api-ref.agora.io/en/chat-sdk/flutter/1.x/file-___Users_dujiepeng_work_flutter_agora_chat_sdk_lib_src_chat_client/ChatClient/loginWithToken.html)
* [`ChatClient.logout`](https://api-ref.agora.io/en/chat-sdk/flutter/1.x/file-___Users_dujiepeng_work_flutter_agora_chat_sdk_lib_src_chat_client/ChatClient/logout.html)
* [`ChatManager.sendMessage`](https://api-ref.agora.io/en/chat-sdk/flutter/1.x/file-___Users_dujiepeng_work_flutter_agora_chat_sdk_lib_src_chat_manager/ChatManager/sendMessage.html)
* [`ChatManager`](https://api-ref.agora.io/en/chat-sdk/flutter/1.x/file-___Users_dujiepeng_work_flutter_agora_chat_sdk_lib_src_chat_manager/ChatManager-class.html)
* [`ChatMessage`](https://api-ref.agora.io/en/chat-sdk/flutter/1.x/file-___Users_dujiepeng_work_flutter_agora_chat_sdk_lib_src_models_chat_message/ChatMessage-class.html)
* [`ConnectionEventHandler`](https://api-ref.agora.io/en/chat-sdk/flutter/1.x/file-___Users_dujiepeng_work_flutter_agora_chat_sdk_lib_src_event_handler_manager_event_handler/ConnectionEventHandler-class.html)
* [`ChatEventHandler`](https://api-ref.agora.io/en/chat-sdk/flutter/1.x/file-___Users_dujiepeng_work_flutter_agora_chat_sdk_lib_src_event_handler_manager_event_handler/ChatEventHandler-class.html)
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="react-native">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="react-native" />
For details, see the [sample code](https://github.com/AgoraIO/Agora-Chat-API-Examples/tree/main/Chat-RN/quick_start_demo) for getting started with Chat.
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="unity">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="unity" />
### API reference [#api-reference-3]
* [`SDKClient.InitWithOptions`](https://api-ref.agora.io/en/chat-sdk/unity/1.x/class_agora_chat_1_1_s_d_k_client.html#a77b332d22c4a2318a15ea127fa2f66dc)
* [`SDKClient.LoginWithAgoraToken()`](https://api-ref.agora.io/en/chat-sdk/unity/1.x/class_agora_chat_1_1_s_d_k_client.html#a66ed705ea3ab6e9af1c410393f297ac0)
* [`SDKClient.Logout()`](https://api-ref.agora.io/en/chat-sdk/unity/1.x/class_agora_chat_1_1_s_d_k_client.html#a98b67e590dbe9f6253449df9e82dd0c8)
* [`Message.CreateTextSendMessage()`](https://api-ref.agora.io/en/chat-sdk/unity/1.x/class_agora_chat_1_1_message.html#aa97fe7e237c0c2c7f2fd22a206985e2b)
* [`IChatManagerDelegate`](https://api-ref.agora.io/en/chat-sdk/unity/1.x/interface_agora_chat_1_1_i_chat_manager_delegate.html)
* [`IConnectionDelegate`](https://api-ref.agora.io/en/chat-sdk/unity/1.x/interface_agora_chat_1_1_i_connection_delegate.html)
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="windows">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="windows" />
* Avoid blocked callbacks
Each callback of a Chat client instance is triggered from internal threads. To avoid blocking internal threads, Agora recommends that you execute your operations in other threads when callbacks are triggered.
### API reference [#api-reference-4]
* [`AgoraChat.SDKClient Class`](https://api-ref.agora.io/en/chat-sdk/windows/1.x/class_agora_chat_1_1_s_d_k_client.html)
* [`SDKClient.InitWithOptions()`](https://api-ref.agora.io/en/chat-sdk/windows/1.x/class_agora_chat_1_1_s_d_k_client.html#a77b332d22c4a2318a15ea127fa2f66dc)
* [`SDKClient.LoginWithAgoraToken()`](https://api-ref.agora.io/en/chat-sdk/windows/1.x/class_agora_chat_1_1_s_d_k_client.html#a66ed705ea3ab6e9af1c410393f297ac0)
* [`SDKClient.Logout()`](https://api-ref.agora.io/en/chat-sdk/windows/1.x/class_agora_chat_1_1_s_d_k_client.html#a98b67e590dbe9f6253449df9e82dd0c8)
* [`AgoraChat.Message Class`](https://api-ref.agora.io/en/chat-sdk/windows/1.x/class_agora_chat_1_1_message.html)
* [`CreateTextSendMessage()`](https://api-ref.agora.io/en/chat-sdk/windows/1.x/class_agora_chat_1_1_message.html#aa97fe7e237c0c2c7f2fd22a206985e2b)
* [`AgoraChat.ChatManager Class`](https://api-ref.agora.io/en/chat-sdk/windows/1.x/class_agora_chat_1_1_chat_manager.html)
* [`ChatManager.SendMessage()`](https://api-ref.agora.io/en/chat-sdk/windows/1.x/class_agora_chat_1_1_chat_manager.html#af9f598e16100bebd4b035d8335262e2b)
* [`AddChatManagerDelegate()`](https://api-ref.agora.io/en/chat-sdk/windows/1.x/class_agora_chat_1_1_chat_manager.html#ad82fe37433d576cbf96af1fe9ad381db)
* [`RemoveChatManagerDelegate()`](https://api-ref.agora.io/en/chat-sdk/windows/1.x/class_agora_chat_1_1_chat_manager.html#af04896f0b9f6569bbd37d044ec7771bc)
* [`AddConnectionDelegate()`](https://api-ref.agora.io/en/chat-sdk/windows/1.x/class_agora_chat_1_1_s_d_k_client.html#a7156cf061b2ac478ebaabb7776c18079)
* [`DeleteConnectionDelegate()`](https://api-ref.agora.io/en/chat-sdk/windows/1.x/class_agora_chat_1_1_s_d_k_client.html#a5dbada1cc1567eb62784bdbae935517a)
<_PlatformProcessedMarker close="true" />
### Next steps [#next-steps]
In a production environment, best practice is to deploy your own token server. Users retrieve a token from the token server to log in to Chat. To see how to implement a server that generates and serves tokens on request, see [Secure authentication with tokens](../develop/authentication).
# UI Kit quickstart (/en/realtime-media/im/get-started-uikit)
<_PlatformTabsGroup groupMode="structured" canonicalPlatform="web" platforms="["windows","unity","android","ios","web","flutter","react-native"]" showTabs="true">
<_PlatformPanel platform="windows">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="windows" />
**Currently, there is no Chat UI Kit for this platform.**
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="unity">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="unity" />
**Currently, there is no Chat UI Kit for this platform.**
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="android">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="android" />
Instant messaging connects people wherever they are and allows them to communicate with others in real time. Agora offers an open-source Chat UI Kit project on GitHub. With built-in user interfaces for key Chat features, the Agora Chat UI Kit enables you to quickly embed real-time messaging into your app without requiring extra effort on the UI.
For the latest Agora Chat UIKit documentation, refer to the [UIKit 2.x Documentation](https://github.com/AgoraLab/agora-chat-uikit) GitHub repository.
Legacy UIkit 1.x documentation
This page shows sample code to add peer-to-peer messaging into your app by using the Agora Chat UI Kit.
## Understand the tech [#understand-the-tech]
The following figure shows the workflow of how clients send and receive peer-to-peer messages:
Chat UI kit workflow

1. Clients retrieve a token from your app server.
2. Client A and Client B log in to Agora Chat.
3. Client A sends a message to Client B. The message is sent to the Agora Chat server, and the server delivers the message to Client B. When Client B receives the message, the SDK triggers an event. Client B listens for the event and gets the message.
## Prerequisites [#prerequisites]
* An Android simulator or a physical Android device.
* Android Studio 3.2 or higher.
* Java Development Kit (JDK). You can refer to the [User Guide of Android](https://developer.android.com/studio/write/java8-support) for applicable versions.
## Project setup [#project-setup]
Follow the steps to create the environment necessary to add video call into your app.
1. For new projects, in **Android Studio**, create a **Phone and Tablet** [Android project](https://developer.android.com/studio/projects/create-project) with an **Empty Activity**.
After creating the project, Android Studio automatically starts gradle sync. Ensure that the sync succeeds before you continue.
2. Integrate the Chat SDK into your project with Maven Central.
1. In `/Gradle Scripts/build.gradle(Project: )`, add the following lines to add the Maven Central dependency:
```java
buildscript {
repositories {
...
mavenCentral()
}
}
allprojects {
repositories {
...
mavenCentral()
}
}
```
2. In `/Gradle Scripts/build.gradle(Module: .app)`, add the following lines to integrate the Chat UI Samples into your Android project:
```java
android {
defaultConfig {
// The Android OS version should be 21 or higher.
minSdkVersion 21
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
...
// Replace X.Y.Z with the latest version of the Chat UI Samples.
// For the latest version, go to https://search.maven.org/.
implementation 'io.agora.rtc:chat-uikit:X.Y.Z'
}
```
3. Add permissions for network and device access.
In `/app/Manifests/AndroidManifest.xml`, add the following permissions after ``:
```xml
```
These are the minimum permissions you need to add to start Chat. You can also add other permissions according to your use case.
4. Prevent code obfuscation.
In `/Gradle Scripts/proguard-rules.pro`, add the following line:
```java
-keep class io.agora.** {*;}
-dontwarn io.agora.**
```
## Implementation [#implementation]
## Implement peer-to-peer messaging [#implement-peer-to-peer-messaging]
This section shows how to use the Chat UI Samples to implement peer-to-peer messaging in your app step by step.
### Create the UI [#create-the-ui]
1. To add the text strings used by the UI, open `app/res/values/strings.xml ` and replace the content with the following:
```xml
AgoraChatUIKitQuickstartUsername or password is emptySign up success!Sign in success!Sign out success!Send message success!Enter usernameEnter passwordSign inSign outSign upEnter to usernameStart chatEnter contentShow log area...An account has been signed in, please sign out first and then sign inPlease sign in firstPlease enter the username who you want to send first!41117440#383391
```
2. To add the UI framework, open `app/res/layout/activity_main.xml` and replace the content with the following:
```xml
```
### Implementation [#implementation-1]
To enable your app to send and receive messages between individual users, do the following:
1. Implement sending and receiving messages.
In `app/java/io.agora.agorachatquickstart/MainActivity`, replace the code with the following:
```java
package io.agora.chatuikitquickstart;
import android.Manifest;
import android.os.Bundle;
import android.text.TextUtils;
import android.text.method.ScrollingMovementMethod;
import android.view.MotionEvent;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import org.json.JSONObject;
import java.util.HashMap;
import java.util.Map;
import io.agora.CallBack;
import io.agora.ConnectionListener;
import io.agora.Error;
import io.agora.chat.ChatClient;
import io.agora.chat.ChatMessage;
import io.agora.chat.ChatOptions;
import io.agora.chat.uikit.EaseUIKit;
import io.agora.chat.uikit.chat.EaseChatFragment;
import io.agora.chat.uikit.chat.interfaces.OnChatExtendMenuItemClickListener;
import io.agora.chat.uikit.chat.interfaces.OnChatInputChangeListener;
import io.agora.chat.uikit.chat.interfaces.OnChatRecordTouchListener;
import io.agora.chat.uikit.chat.interfaces.OnMessageSendCallBack;
import io.agora.chatuikitquickstart.utils.LogUtils;
import io.agora.chatuikitquickstart.utils.PermissionsManager;
import io.agora.cloud.HttpClientManager;
import io.agora.cloud.HttpResponse;
import io.agora.util.EMLog;
import static io.agora.cloud.HttpClientManager.Method_POST;
public class MainActivity extends AppCompatActivity {
private static final String NEW_LOGIN = "NEW_LOGIN";
private static final String RENEW_TOKEN = "RENEW_TOKEN";
private static final String LOGIN_URL = "https://a41.chat.agora.io/app/chat/user/login";
private static final String REGISTER_URL = "https://a41.chat.agora.io/app/chat/user/register";
private EditText et_username;
private TextView tv_log;
private ConnectionListener connectionListener;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
requestPermissions();
initSDK();
addConnectionListener();
}
private void initView() {
et_username = findViewById(R.id.et_username);
tv_log = findViewById(R.id.tv_log);
tv_log.setMovementMethod(new ScrollingMovementMethod());
}
private void requestPermissions() {
checkPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE, 110);
}
//=================== init SDK start ========================
private void initSDK() {
ChatOptions options = new ChatOptions();
// Set your appkey applied from Agora Console
String sdkAppkey = getString(R.string.app_key);
if(TextUtils.isEmpty(sdkAppkey)) {
Toast.makeText(MainActivity.this, "You should set your AppKey first!", Toast.LENGTH_SHORT).show();
return;
}
// Set your appkey to options
options.setAppKey(sdkAppkey);
// Set whether confirmation of delivery is required by the recipient. Default: false
options.setRequireDeliveryAck(true);
// Set not to log in automatically
options.setAutoLogin(false);
// Use UI Samples to initialize Chat SDK
EaseUIKit.getInstance().init(this, options);
// Make Chat SDK debuggable
ChatClient.getInstance().setDebugMode(true);
}
//=================== init SDK end ========================
//================= SDK listener start ====================
private void addConnectionListener() {
connectionListener = new ConnectionListener() {
@Override
public void onConnected() {
}
@Override
public void onDisconnected(int error) {
if (error == Error.USER_REMOVED) {
onUserException("account_removed");
} else if (error == Error.USER_LOGIN_ANOTHER_DEVICE) {
onUserException("account_conflict");
} else if (error == Error.SERVER_SERVICE_RESTRICTED) {
onUserException("account_forbidden");
} else if (error == Error.USER_KICKED_BY_CHANGE_PASSWORD) {
onUserException("account_kicked_by_change_password");
} else if (error == Error.USER_KICKED_BY_OTHER_DEVICE) {
onUserException("account_kicked_by_other_device");
} else if(error == Error.USER_BIND_ANOTHER_DEVICE) {
onUserException("user_bind_another_device");
} else if(error == Error.USER_DEVICE_CHANGED) {
onUserException("user_device_changed");
} else if(error == Error.USER_LOGIN_TOO_MANY_DEVICES) {
onUserException("user_login_too_many_devices");
}
}
@Override
public void onTokenExpired() {
//login again
signInWithToken(null);
LogUtils.showLog(tv_log,"ConnectionListener onTokenExpired");
}
@Override
public void onTokenWillExpire() {
getTokenFromAppServer(RENEW_TOKEN);
LogUtils.showLog(tv_log, "ConnectionListener onTokenWillExpire");
}
};
// Call removeConnectionListener(connectionListener) when the activity is destroyed
ChatClient.getInstance().addConnectionListener(connectionListener);
}
//================= SDK listener end ====================
//=================== click event start ========================
/**
* Sign up with username and password.
*/
public void signUp(View view) {
String username = et_username.getText().toString().trim();
String pwd = ((EditText) findViewById(R.id.et_pwd)).getText().toString().trim();
if(TextUtils.isEmpty(username) || TextUtils.isEmpty(pwd)) {
LogUtils.showErrorToast(this, tv_log, getString(R.string.username_or_pwd_miss));
return;
}
execute(()-> {
try {
Map headers = new HashMap<>();
headers.put("Content-Type", "application/json");
JSONObject request = new JSONObject();
request.putOpt("userAccount", username);
request.putOpt("userPassword", pwd);
LogUtils.showErrorLog(tv_log,"begin to signUp...");
HttpResponse response = HttpClientManager.httpExecute(REGISTER_URL, headers, request.toString(), Method_POST);
int code= response.code;
String responseInfo = response.content;
if (code == 200) {
if (responseInfo != null && responseInfo.length() > 0) {
JSONObject object = new JSONObject(responseInfo);
String resultCode = object.getString("code");
if(resultCode.equals("RES_OK")) {
LogUtils.showToast(MainActivity.this, tv_log, getString(R.string.sign_up_success));
}else{
String errorInfo = object.getString("errorInfo");
LogUtils.showErrorLog(tv_log,errorInfo);
}
} else {
LogUtils.showErrorLog(tv_log,responseInfo);
}
} else {
LogUtils.showErrorLog(tv_log,responseInfo);
}
} catch (Exception e) {
e.printStackTrace();
LogUtils.showErrorLog(tv_log, e.getMessage());
}
});
}
/**
* Log in with token.
*/
public void signInWithToken(View view) {
getTokenFromAppServer(NEW_LOGIN);
}
/**
* Sign out.
*/
public void signOut(View view) {
if(ChatClient.getInstance().isLoggedInBefore()) {
ChatClient.getInstance().logout(true, new CallBack() {
@Override
public void onSuccess() {
LogUtils.showToast(MainActivity.this, tv_log, getString(R.string.sign_out_success));
}
@Override
public void onError(int code, String error) {
LogUtils.showErrorToast(MainActivity.this, tv_log, "Sign out failed! code: "+code + " error: "+error);
}
@Override
public void onProgress(int progress, String status) {
}
});
}
}
public void startChat(View view) {
EditText et_to_username = findViewById(R.id.et_to_username);
String toChatUsername = et_to_username.getText().toString().trim();
// check username
if(TextUtils.isEmpty(toChatUsername)) {
LogUtils.showErrorToast(this, tv_log, getString(R.string.not_find_send_name));
return;
}
// 1: single chat; 2: group chat; 3: chat room
EaseChatFragment fragment = new EaseChatFragment.Builder(toChatUsername, EaseChatType.SINGLE_CHAT)
.useHeader(false)
.setOnChatExtendMenuItemClickListener(new OnChatExtendMenuItemClickListener() {
@Override
public boolean onChatExtendMenuItemClick(View view, int itemId) {
if(itemId == io.agora.chat.uikit.R.id.extend_item_take_picture) {
return !checkPermissions(Manifest.permission.CAMERA, 111);
}else if(itemId == io.agora.chat.uikit.R.id.extend_item_picture || itemId == io.agora.chat.uikit.R.id.extend_item_file || itemId == io.agora.chat.uikit.R.id.extend_item_video) {
return !checkPermissions(Manifest.permission.READ_EXTERNAL_STORAGE, 112);
}
return false;
}
})
.setOnChatRecordTouchListener(new OnChatRecordTouchListener() {
@Override
public boolean onRecordTouch(View v, MotionEvent event) {
return !checkPermissions(Manifest.permission.RECORD_AUDIO, 113);
}
})
.setOnMessageSendCallBack(new OnMessageSendCallBack() {
@Override
public void onSuccess(ChatMessage message) {
LogUtils.showLog(tv_log, "Send success: message type: " + message.getType().name());
}
@Override
public void onError(int code, String errorMsg) {
LogUtils.showErrorLog(tv_log, "Send failed: error code: "+code + " errorMsg: "+errorMsg);
}
})
.build();
getSupportFragmentManager().beginTransaction().replace(R.id.fl_fragment, fragment).commit();
}
//=================== click event end ========================
//=================== get token from server start ========================
private void getTokenFromAppServer(String requestType) {
if(ChatClient.getInstance().getOptions().getAutoLogin() && ChatClient.getInstance().isLoggedInBefore()) {
LogUtils.showErrorLog(tv_log, getString(R.string.has_login_before));
return;
}
String username = et_username.getText().toString().trim();
String pwd = ((EditText) findViewById(R.id.et_pwd)).getText().toString().trim();
if(TextUtils.isEmpty(username) || TextUtils.isEmpty(pwd)) {
LogUtils.showErrorToast(MainActivity.this, tv_log, getString(R.string.username_or_pwd_miss));
return;
}
execute(()-> {
try {
Map headers = new HashMap<>();
headers.put("Content-Type", "application/json");
JSONObject request = new JSONObject();
request.putOpt("userAccount", username);
request.putOpt("userPassword", pwd);
LogUtils.showErrorLog(tv_log,"begin to getTokenFromAppServer ...");
HttpResponse response = HttpClientManager.httpExecute(LOGIN_URL, headers, request.toString(), Method_POST);
int code = response.code;
String responseInfo = response.content;
if (code == 200) {
if (responseInfo != null && responseInfo.length() > 0) {
JSONObject object = new JSONObject(responseInfo);
String token = object.getString("accessToken");
if(TextUtils.equals(requestType, NEW_LOGIN)) {
ChatClient.getInstance().loginWithAgoraToken(username, token, new CallBack() {
@Override
public void onSuccess() {
LogUtils.showToast(MainActivity.this, tv_log, getString(R.string.sign_in_success));
}
@Override
public void onError(int code, String error) {
LogUtils.showErrorToast(MainActivity.this, tv_log, "Login failed! code: " + code + " error: " + error);
}
@Override
public void onProgress(int progress, String status) {
}
});
}else if(TextUtils.equals(requestType, RENEW_TOKEN)) {
ChatClient.getInstance().renewToken(token);
}
} else {
LogUtils.showErrorToast(MainActivity.this, tv_log, "getTokenFromAppServer failed! code: " + code + " error: " + responseInfo);
}
} else {
LogUtils.showErrorToast(MainActivity.this, tv_log, "getTokenFromAppServer failed! code: " + code + " error: " + responseInfo);
}
} catch (Exception e) {
e.printStackTrace();
LogUtils.showErrorToast(MainActivity.this, tv_log, "getTokenFromAppServer failed! code: " + 0 + " error: " + e.getMessage());
}
});
}
//=================== get token from server end ========================
/**
* Check and request permission
* @param permission
* @param requestCode
* @return
*/
private boolean checkPermissions(String permission, int requestCode) {
if(!PermissionsManager.getInstance().hasPermission(this, permission)) {
PermissionsManager.getInstance().requestPermissions(this, new String[]{permission}, requestCode);
return false;
}
return true;
}
/**
* user met some exception: conflict, removed or forbidden, goto login activity
*/
protected void onUserException(String exception) {
LogUtils.showLog(tv_log, "onUserException: " + exception);
ChatClient.getInstance().logout(false, null);
}
public void execute(Runnable runnable) {
new Thread(runnable).start();
}
@Override
protected void onDestroy() {
super.onDestroy();
if(connectionListener != null) {
ChatClient.getInstance().removeConnectionListener(connectionListener);
}
}
}
```
2. Add LogUtils and PermissionsManager.
To make troubleshooting less time-consuming, this quickstart also uses `LogUtils` class for logs. Navigate to `app/java/io.agora.agorachatquickstart/`, create a folder named `utils`. In this new folder, create a `.java` file, name it `LogUtils`, and copy the following codes into the file.
```java
package io.agora.chatuikitquickstart.utils;
import android.app.Activity;
import android.text.TextUtils;
import android.util.Log;
import android.widget.TextView;
import android.widget.Toast;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
public class LogUtils {
private static final String TAG = LogUtils.class.getSimpleName();
public static void showErrorLog(TextView tvLog, String content) {
showLog(tvLog, content);
}
public static void showNormalLog(TextView tvLog, String content) {
showLog(tvLog, content);
}
public static void showLog(TextView tvLog, String content) {
if(TextUtils.isEmpty(content) || tvLog == null) {
return;
}
String preContent = tvLog.getText().toString().trim();
StringBuilder builder = new StringBuilder();
builder.append(formatCurrentTime())
.append(" ")
.append(content)
.append("\n")
.append(preContent);
tvLog.post(()-> {
tvLog.setText(builder);
});
}
public static void showErrorToast(Activity activity, TextView tvLog, String content) {
if(activity == null || activity.isFinishing()) {
Log.e(TAG, "Context is null...");
return;
}
if(TextUtils.isEmpty(content)) {
return;
}
activity.runOnUiThread(()-> {
Toast.makeText(activity, content, Toast.LENGTH_SHORT).show();
showErrorLog(tvLog,content);
});
}
public static void showToast(Activity activity, TextView tvLog, String content) {
if(TextUtils.isEmpty(content) || activity == null || activity.isFinishing()) {
return;
}
activity.runOnUiThread(()-> {
Toast.makeText(activity, content, Toast.LENGTH_SHORT).show();
showNormalLog(tvLog, content);
});
}
private static String formatCurrentTime() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
return sdf.format(new Date());
}
}
```
When your app launches, check if the permissions necessary to insert real-time chat into the app are granted. In the `utils` file, create a `.java` file, name it `PermissionsManager`, and copy the following codes into the file.
```java
package io.agora.chatuikitquickstart.utils;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
public class PermissionsManager {
private static PermissionsManager mInstance = null;
public static PermissionsManager getInstance() {
if (mInstance == null) {
mInstance = new PermissionsManager();
}
return mInstance;
}
private PermissionsManager() {}
/**
* Check if has permission
* @param context
* @param permission
* @return
*/
@SuppressWarnings("unused")
public synchronized boolean hasPermission(@Nullable Context context, @NonNull String permission) {
return context != null && ActivityCompat.checkSelfPermission(context, permission)
== PackageManager.PERMISSION_GRANTED;
}
/**
* Request permissions
* @param activity
* @param permissions
* @param requestCode
*/
public synchronized void requestPermissions(Activity activity, String[] permissions, int requestCode) {
ActivityCompat.requestPermissions(activity, permissions, requestCode);
}
}
```
3. To send image and file messages, take the following configurations:
Under `/app/src/main/res/`, create a folder, name it `xml`, and create an xml file named `file_paths.xml` under `xml`. Open `file_paths.xml`, replace the code with the following:
```xml
```
In `/app/Manifests/AndroidManifest.xml`, add the following lines before ``:
```xml
```
4. Click `Sync Project with Gradle Files` to sync your project. Now you are ready to test your app.
## Test your app [#test-your-app]
To validate the peer-to-peer messaging you have just integrated into your app using Chat:
1. In Android Studio, click `Run 'app'`.
You see the following interface on your simulator or physical device:
2. Create a user account and click **SIGN UP**. Click **Sign in** and you will see a log that says Sign in success.
3. Run the app on another Android device or simulator and create another user account. Ensure that the usernames you created are unique.
4. On the first device or simulator, enter the username you just created and click **START CHAT**. You can now start chatting between the two clients.
## Next steps [#next-steps]
For demonstration purposes, Chat provides an app server that enables you to quickly retrieve a token using the App Key given in this guide. In a production context, the best practice is for you to deploy your own token server, use your own [App Key](./enable#get-the-information-of-the-agora-chat-project) to generate a token, and retrieve the token on the client side to log in to Agora. To see how to implement a server that generates and serves tokens on request, see [Authenticate your users with tokens](/en/realtime-media/im/build/secure-access-and-authentication/authentication).
## Reference [#reference]
Agora provides the fully featured [AgoraChat-Starter-Kit-Android](https://github.com/AgoraIO-Usecase/AgoraChat-UIKit-android) demo app as an implementation reference.
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="ios">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="ios" />
Instant messaging connects people wherever they are and allows them to communicate with others in real time. Agora offers an open-source Chat UI Kit project on GitHub. With built-in user interfaces for key Chat features, the Agora Chat UI Kit enables you to quickly embed real-time messaging into your app without requiring extra effort on the UI.
For the latest Agora Chat UIKit documentation, refer to the [UIKit 2.x Documentation](https://github.com/AgoraLab/agora-chat-uikit) GitHub repository.
Legacy UIkit 1.x documentation
This page shows sample code to add peer-to-peer messaging into your app by using the Agora Chat UI Kit.
## Understand the tech [#understand-the-tech-1]
The following figure shows the workflow of how clients send and receive peer-to-peer messages:
Chat UI kit workflow

1. Clients retrieve a token from your app server.
2. Client A and Client B log in to Agora Chat.
3. Client A sends a message to Client B. The message is sent to the Agora Chat server, and the server delivers the message to Client B. When Client B receives the message, the SDK triggers an event. Client B listens for the event and gets the message.
## Prerequisites [#prerequisites-1]
* Xcode, preferably the latest version.
* A simulator or a physical mobile device running iOS 11.0 or later.
* CocoaPods. Refer to [Getting Started with CocoaPods](https://guides.cocoapods.org/using/getting-started.html#getting-started) if you have not installed CocoaPods.
## Project setup [#project-setup-1]
In order to create the environment necessary to integrate Chat UI Samples into your app, do the following:
1. [Create a new project](https://help.apple.com/xcode/mac/current/#/dev07db0e578) for an iOS app. Make sure you select **Storyboard** as the **Interface**.
If you have added any team information, you see the **Add account...** button. Click it, input your Apple ID, and click Next. Your team information is added to the project.
2. [Enable 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) to deploy your iOS app.
4. Add project permissions for microphone and camera usage.
Open **info** in the project navigation panel, and add the following properties to the [Property List](https://help.apple.com/xcode/mac/current/#/dev3f399a2a6):
| Key | Type | Value |
| --------------------------------------------------------- | ------- | ------------------------ |
| `Privacy - Photo Library Usage Description` | String | For photo library access |
| `Privacy - Microphone Usage Description` | String | For microphone access |
| `Privacy - Camera Usage Description` | String | For camera access |
| `App Transport Security Settings > Allow Arbitrary Loads` | Boolean | YES |
5. Integrate the UI Samples into your project.
1. Create a Podfile for your project.
In the Terminal, navigate to the root directory of your Xcode project and run the following command:
```bash
pod init
```
2. Add dependencies to the UI Samples in the Podfile.
Open Podfile and replace the content with the following:
```bash
platform :ios, '11.0'
target 'EaseChatKitExample' do
# Pods for EaseChatKitExample
pod 'chat-uikit'
pod 'Masonry'
end
```
6. Install the UI Samples.
In the Terminal, navigate to the root directory of your Xcode project and run the following command:
```bash
pod install
```
The Chat UI Samples is now added into your project.
## Implementation [#implementation-2]
## Implement peer-to-peer messaging [#implement-peer-to-peer-messaging-1]
This section shows how to use Chat UI Samples to rapidly implement peer-to-peer messaging in your app.
### Initialize UI Samples [#initialize-ui-samples]
Follow the steps to initialize the UI Samples.
1. To import the header file, open `SceneDelegate.m` in **Xcode** and add the following lines to import the header file:
```objc
#import
#import "AgoraLoginViewController.h"
#import
```
2. Initialize the UI Samples by calling the `initWithAgoraChatOptions` method. In `SceneDelegate.m`, replace the `willConnectToSession` method with the following code:
```objc
- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions {
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
AgoraChatOptions *options = [AgoraChatOptions optionsWithAppkey:@"41117440#383391"];
options.enableConsoleLog = YES;
options.usingHttpsOnly = YES;
options.enableDeliveryAck = YES;
options.isAutoLogin = NO;
// Initialize chat-starter-kit
[EaseChatKitManager initWithAgoraChatOptions:options];
UIWindowScene *windowScene = (UIWindowScene *)scene;
self.window = [[UIWindow alloc] initWithWindowScene:windowScene];
self.window.frame = windowScene.coordinateSpace.bounds;
// Go to the login view
self.window.rootViewController = [[AgoraLoginViewController alloc]init];
[self.window makeKeyAndVisible];
}
```
### Log into Chat [#log-into-chat]
Take the following steps to log into Chat.
1. In **Xcode**, go to **File** > **New** > **File**, and create a **Cocoa Touch Class** file named `AgoraChatHttpRequest`. Make sure to set **Subclass of** as `NSObject`. Save the file under **AgoraChatUIKit**.

2. Open `AgoraChatHttpRequest.h` and replace the code with the following:
```objc
#import
NS_ASSUME_NONNULL_BEGIN
@interface AgoraChatHttpRequest : NSObject
+ (instancetype)sharedManager;
- (void)registerToApperServer:(NSString *)uName
pwd:(NSString *)pwd
completion:(void (^)(NSInteger statusCode, NSString *response))aCompletionBlock;
- (void)loginToApperServer:(NSString *)uName
pwd:(NSString *)pwd
completion:(void (^)(NSInteger statusCode, NSString *response))aCompletionBlock;
@end
NS_ASSUME_NONNULL_END
```
To register to the Chat app server, open `AgoraChatHttpRequest.m` and replace the code with the following:
```objc
#import "AgoraChatHttpRequest.h"
@interface AgoraChatHttpRequest()
@property (readonly, nonatomic, strong) NSURLSession *session;
@end
@implementation AgoraChatHttpRequest
+ (instancetype)sharedManager
{
static dispatch_once_t onceToken;
static AgoraChatHttpRequest *sharedInstance;
dispatch_once(&onceToken, ^{
sharedInstance = [[AgoraChatHttpRequest alloc] init];
});
return sharedInstance;
}
- (instancetype)init
{
if (self = [super init]) {
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
configuration.timeoutIntervalForRequest = 120;
_session = [NSURLSession sessionWithConfiguration:configuration
delegate:self
delegateQueue:[NSOperationQueue mainQueue]];
}
return self;
}
- (void)registerToApperServer:(NSString *)uName
pwd:(NSString *)pwd
completion:(void (^)(NSInteger statusCode, NSString *aUsername))aCompletionBlock
{
NSURL *url = [NSURL URLWithString:@"https://a41.chat.agora.io/app/chat/user/register"];
NSMutableURLRequest *request = [NSMutableURLRequest
requestWithURL:url];
request.HTTPMethod = @"POST";
NSMutableDictionary *headerDict = [[NSMutableDictionary alloc]init];
[headerDict setObject:@"application/json" forKey:@"Content-Type"];
request.allHTTPHeaderFields = headerDict;
NSMutableDictionary *dict = [[NSMutableDictionary alloc]init];
[dict setObject:uName forKey:@"userAccount"];
[dict setObject:pwd forKey:@"userPassword"];
request.HTTPBody = [NSJSONSerialization dataWithJSONObject:dict options:0 error:nil];
NSURLSessionDataTask *task = [self.session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSString *responseData = data ? [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] : nil;
if (aCompletionBlock) {
aCompletionBlock(((NSHTTPURLResponse*)response).statusCode, responseData);
}
}];
[task resume];
}
- (void)loginToApperServer:(NSString *)uName
pwd:(NSString *)pwd
completion:(void (^)(NSInteger statusCode, NSString *response))aCompletionBlock
{
NSURL *url = [NSURL URLWithString:@"https://a41.chat.agora.io/app/chat/user/login"];
NSMutableURLRequest *request = [NSMutableURLRequest
requestWithURL:url];
request.HTTPMethod = @"POST";
NSMutableDictionary *headerDict = [[NSMutableDictionary alloc]init];
[headerDict setObject:@"application/json" forKey:@"Content-Type"];
request.allHTTPHeaderFields = headerDict;
NSMutableDictionary *dict = [[NSMutableDictionary alloc]init];
[dict setObject:uName forKey:@"userAccount"];
[dict setObject:pwd forKey:@"userPassword"];
request.HTTPBody = [NSJSONSerialization dataWithJSONObject:dict options:0 error:nil];
NSURLSessionDataTask *task = [self.session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSString *responseData = data ? [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] : nil;
if (aCompletionBlock) {
aCompletionBlock(((NSHTTPURLResponse*)response).statusCode, responseData);
}
}];
[task resume];
}
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler
{
if([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]){
NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
if(completionHandler)
completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
}
}
@end
```
3. Go to **File** > **New** > **File** and create a **Cocoa Touch Class** file named `AgoraLoginViewController`. Make sure to set **Subclass of** as `UIViewController`.
4. To import the header file, open `AgoraLoginViewController.m` and add the following lines:
```objc
#import "AgoraChatHttpRequest.h" // To send request to the Appserver
#import //Chat SDK
#import "ViewController.h"
```
To sign up, add the following lines after `@implementation AgoraLoginViewController`:
```objc
// Register to the AppServer
- (void)doSignUp {
// Set the chat ID as vvv
[[AgoraChatHttpRequest sharedManager] registerToApperServer:@"vvv" pwd:@"1" completion:^(NSInteger statusCode, NSString * _Nonnull response) {
dispatch_async(dispatch_get_main_queue(),^{
if (response != nil) {
NSData *responseData = [response dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *responsedict = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:nil];
if (responsedict != nil) {
NSString *result = [responsedict objectForKey:@"code"];
if ([result isEqualToString:@"RES_OK"]) {
// Sign in Chat if registration succeeds
[self doSignIn];
}
}
}
});
}];
}
- (void)doSignIn {
// Sign in AppServer
[[AgoraChatHttpRequest sharedManager] loginToApperServer:@"vvv" pwd:@"1" completion:^(NSInteger statusCode, NSString * _Nonnull response) {
dispatch_async(dispatch_get_main_queue(), ^{
if (response && response.length > 0 && statusCode) {
NSData *responseData = [response dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *responsedict = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:nil];
NSString *token = [responsedict objectForKey:@"accessToken"];
NSString *loginName = [responsedict objectForKey:@"chatUserName"];
if (token && token.length > 0) {
// Log into Chat SDK
[[AgoraChatClient sharedClient] loginWithUsername:[loginName lowercaseString] agoraToken:token completion:^(NSString *aUsername, AgoraChatError *aError) {
if (!aError) {
ViewController *chatsVC = [[ViewController alloc] init];
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:chatsVC];
navigationController.navigationBarHidden = YES;
UIWindow *window = [[[UIApplication sharedApplication] windows] lastObject];
window.rootViewController = navigationController;
}
}];
}
}
});
}];
}
```
Replace the `viewDidLoad` function with the following code:
```objc
- (void)viewDidLoad {
[super viewDidLoad];
[self doSignIn];
// Do any additional setup after loading the view.
}
```
### Launch the chat storyboard [#launch-the-chat-storyboard]
In this section, add an input box for getting username of the peer user, and a **Chat** button that loads the chat storyboard.
In `ViewController.m`, add the following code:
```objc
#define kIsBangsScreen ({\
BOOL isBangsScreen = NO; \
if (@available(iOS 11.0, *)) { \
UIWindow *window = [[UIApplication sharedApplication].windows firstObject]; \
isBangsScreen = window.safeAreaInsets.bottom > 0; \
} \
isBangsScreen; \
})
#define AgoraChatVIEWTOPMARGIN (kIsBangsScreen ? 34.f : 0.f)
#import "ViewController.h"
#import
#import
#import
@interface ViewController ()
@property (nonatomic, strong) EaseConversationModel *conversationModel;
@property (nonatomic, strong) AgoraChatConversation *conversation;
@property (nonatomic, strong) EaseChatViewController *chatController;
@property (nonatomic, strong) UITextField *conversationIdField;
@property (nonatomic, strong) UIButton *chatBtn;
@property (nonatomic, strong) UIButton *logoutBtn;
@end
```
In `ViewController.m`, replace the `viewDidLoad` method with the following code:
```objc
- (void)viewDidLoad {
[super viewDidLoad];
[self _setupChatSubviews];
}
- (void)viewWillAppear:(BOOL)animated
{
self.navigationController.navigationBarHidden = YES;
}
- (void)_setupChatSubviews
{
self.conversationIdField = [[UITextField alloc] init];
self.conversationIdField.backgroundColor = [UIColor systemGrayColor];
self.conversationIdField.delegate = self;
self.conversationIdField.borderStyle = UITextBorderStyleNone;
NSAttributedString *convAttrStr = [[NSAttributedString alloc] initWithString:@"single chat ID" attributes:@{NSForegroundColorAttributeName:[UIColor whiteColor]}];
self.conversationIdField.attributedPlaceholder = convAttrStr;
self.conversationIdField.font = [UIFont systemFontOfSize:17];
self.conversationIdField.textColor = [UIColor whiteColor];
self.conversationIdField.returnKeyType = UIReturnKeyDone;
self.conversationIdField.layer.cornerRadius = 5;
self.conversationIdField.layer.borderWidth = 1;
self.conversationIdField.layer.borderColor = [UIColor lightGrayColor].CGColor;
[self.view addSubview:self.conversationIdField];
[self.conversationIdField mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.view).offset(30);
make.top.equalTo(self.view).offset(30 + AgoraChatVIEWTOPMARGIN);
make.height.mas_equalTo(@50);
make.width.mas_equalTo(@320);
}];
self.chatBtn = [[UIButton alloc] init];
self.chatBtn.clipsToBounds = YES;
self.chatBtn.layer.cornerRadius = 5;
self.chatBtn.backgroundColor = [UIColor colorWithRed:((float) 78 / 255.0f) green:0 blue:((float) 234 / 255.0f) alpha:1];
self.chatBtn.titleLabel.font = [UIFont systemFontOfSize:19];
[self.chatBtn setTitle:@"chat" forState:UIControlStateNormal];
[self.chatBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[self.chatBtn addTarget:self action:@selector(chatAction) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:self.chatBtn];
[self.chatBtn mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.view).offset(30);
make.top.equalTo(self.conversationIdField.mas_bottom).offset(20);
make.height.mas_equalTo(@50);
make.width.mas_equalTo(@150);
}];
self.logoutBtn = [[UIButton alloc]init];
self.logoutBtn.backgroundColor = [UIColor redColor];
[self.logoutBtn setTitle:@"Log out" forState:UIControlStateNormal];
[self.logoutBtn addTarget:self action:@selector(logout) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:self.logoutBtn];
[self.logoutBtn mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.bottom.equalTo(self.view);
make.height.equalTo(@50);
}];
self.view.backgroundColor = [UIColor colorWithRed:242/255.0 green:242/255.0 blue:242/255.0 alpha:1.0];
}
```
Add the following code in `ViewController.m` to create the input box and **Chat** button:
```objc
- (void)chatAction
{
[self.view endEditing:YES];
if (self.conversationIdField.text.length
);
}
}
export default App;
```
2. Set the layout for the conversation.
Open `my-app/src/App.css`, and replace the content with the following:
```javascript
/** App.css */
.container {
height: 100%;
width: 100%
}
```
## Test your app [#test-your-app-1]
When the app launches, you see the following interface and are logged into Chat.

To test the app, take the following steps:
1. Enter the **single chat ID**. A single chat ID is the user ID of a peer user and for testing purposes, you can simply input `abc`.
2. Click **chat**. An edit box pops up.
3. Enter the message in the edit box and click **Enter**, the message is sent and displayed in the UI.
4. You can also click **+** on the right of the edit box and try sending attachment messages.
## Next steps [#next-steps-1]
## Reference [#reference-1]
Agora provides the fully featured [AgoraChat-ios](https://github.com/AgoraIO-Usecase/AgoraChat-ios) demo app as an implementation reference.
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="web">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="web" />
Instant messaging connects people wherever they are and allows them to communicate with others in real time. Agora offers an open-source Chat UI Kit project on GitHub. With built-in user interfaces for key Chat features, the Agora Chat UI Kit enables you to quickly embed real-time messaging into your app without requiring extra effort on the UI.
For the latest Agora Chat UIKit documentation, refer to the [UIKit 2.x Documentation](https://github.com/AgoraLab/agora-chat-uikit) GitHub repository.
Legacy UIkit 1.x documentation
This page shows sample code to add peer-to-peer messaging into your app by using the Agora Chat UI Kit.
## Understand the tech [#understand-the-tech-2]
The following figure shows the workflow of how clients send and receive peer-to-peer messages:
Chat UI kit workflow

1. Clients retrieve a token from your app server.
2. Client A and Client B log in to Agora Chat.
3. Client A sends a message to Client B. The message is sent to the Agora Chat server, and the server delivers the message to Client B. When Client B receives the message, the SDK triggers an event. Client B listens for the event and gets the message.
## Prerequisites [#prerequisites-2]
* React 16.8.0 or later.
* React DOM 16.8.0 or later.
* A Windows or macOS computer that has a browser supported by the Agora Chat SDK:
* Internet Explorer 11 or later
* Firefox 10 or later
* Chrome 54 or later
* Safari 11 or later
* A valid [Agora Account](https://console.agora.io/v2).
* An Agora project that has [enabled the Chat service](./enable#enable-the-agora-chat-service).
* An [App key](./enable#get-the-information-of-the-agora-chat-project) and [a user token generated on your app server](/en/realtime-media/im/build/secure-access-and-authentication/authentication).
## Project setup [#project-setup-2]
This sections introduces how to create an app and add the Chat UI Samples to the project.
1. In your terminal, run the following command to create an app:
```bash
# Install CLI
npm install create-react-app
# Create an app named my-app
npx create-react-app my-app
cd my-app
```
Once you successfully create the app, the project structure is as follows:
```bash
my-app
├── package.json
├── public # The static directory for Webpack
│ ├── favicon.ico
│ ├── index.html # The default one-page app
│ └── manifest.json
├── src
│ ├── App.css # The css file of the app
│ ├── App.js # The code of the app
│ ├── App.test.js
│ ├── index.css # The layout when launching the file
│ ├── index.js # Launch the file
│ ├── logo.svg
│ └── serviceWorker.js
└── yarn.lock
```
2. Run one of the following commands to add the Chat UI Samples to your project.
To add the UI Samples using npm:
```bash
npm install agora-chat-uikit --save
```
To add the UI Samples using yarn
```bash
yarn add agora-chat-uikit
```
## Implementation [#implementation-3]
## Implement peer-to-peer messaging [#implement-peer-to-peer-messaging-2]
## Test your app [#test-your-app-2]
In your terminal, run the following command to launch the app:
```bash
npm run start
```
You can see the app launch in your browser. Before you can send a message, refer to [Add a contact](/en/realtime-media/im/build/build-core-messaging/user-attributes#manage-contacts) or [Join a chat group](/en/realtime-media/im/build/build-groups-rooms-and-threads/chat-group/manage-chat-groups#join-and-leave-a-chat-group) to add a contact or join a chat group.
## Next steps [#next-steps-2]
This section includes more advanced features you can implement in your project.
### Customizable attributes [#customizable-attributes]
`EaseChat` provides the following attributes for customization. You can customize the features and layout by setting these attributes. To ensure the functionality of `EaseChat`, ensure that you set all the required parameters.
| Attribute | Type | Required | Description |
| ---------------------- | ----------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `appkey` | String | Yes | The unique identifier that the Chat service assigns to each app. The rule is `$(OrgName)#{AppName}`. |
| `username` | String | Yes | The user ID. |
| `agoraToken` | String | Yes | The Agora token. |
| `to` | String | Yes | In one-to-one messaging, it is the user ID of the recipient; in group chat, it is the group ID. |
| `showByselfAvatar` | Boolean | No | Whether to display the avatar of the current user. `true`: Yes (Default). `false`: No. |
| `easeInputMenu` | String | No | The mode of the input menu. (Default) `all`: The complete mode. `noAudio`: No audio. `noEmoji`: No emoji. `noAudioAndEmoji`: No audio or emoji. `onlyText`: Only text. |
| `menuList` | Array | No | The extensions of the input box on the right panel. (Default) `menuList`: `[ {name:'Send a pic', value:'img'},{name:'Send a file', value:'file'}]` |
| `handleMenuItem` | `function({item, key}`) | No | The callback event triggered by clicking on the right panel of the input box. |
| `successLoginCallback` | function(res) | No | The callback event for a successful login. |
| `failCallback` | function(err) | No | The callback event for a failed method call. |
### Add business logic [#add-business-logic]
In use-cases where you want to add your own business logic, you can use the various callback events provided by the Chat UI Samples, as shown in the following steps:
1. Get the SDK instance
```javascript
const WebIM = EaseApp.getSdk({ appkey: 'xxxx' })
```
2. Add callback events
Call `addEventHandler` to add the callback events.
```javascript
// Use nameSpace to differentiate the different business logics. You can add multiple callback events according to your needs.。
WebIM.conn.addEventHandler('nameSpace'),{
onOpend:()=>{},
onTextMessage:()=>{},
.... })
```
Refer to [EventHandlerType](https://api-ref.agora.io/en/chat-sdk/web/1.x/interfaces/Types.EventHandlerType.EventHandlerType.html) for the complete list of callback events you can add.
## Reference [#reference-2]
Agora Chat provides an open-source [AgoraChat](https://github.com/AgoraIO-Usecase/AgoraChat-web) sample project on GitHub that has integrated the UI Samples. You can download the project to try it out or view the source code.
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="flutter">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="flutter" />
Instant messaging connects people wherever they are and allows them to communicate with others in real time. Agora offers an open-source Chat UI Kit project on GitHub. With built-in user interfaces for key Chat features, the Agora Chat UI Kit enables you to quickly embed real-time messaging into your app without requiring extra effort on the UI.
For the latest Agora Chat UIKit documentation, refer to the [UIKit 2.x Documentation](https://github.com/AgoraLab/agora-chat-uikit) GitHub repository.
Legacy UIkit 1.x documentation
This page shows sample code to add peer-to-peer messaging into your app by using the Agora Chat UI Kit.
## Understand the tech [#understand-the-tech-3]
The following figure shows the workflow of how clients send and receive peer-to-peer messages:
Chat UI kit workflow

1. Clients retrieve a token from your app server.
2. Client A and Client B log in to Agora Chat.
3. Client A sends a message to Client B. The message is sent to the Agora Chat server, and the server delivers the message to Client B. When Client B receives the message, the SDK triggers an event. Client B listens for the event and gets the message.
## Prerequisites [#prerequisites-3]
* Flutter 2.10 or higher.
* Dart 2.16 or higher.
* If your target platform is iOS:
* macOS
* Xcode 12.4 or higher with Xcode Command Line Tools
* CocoaPods
* An iOS emulator or a physical iOS device running iOS 10.0 or higher
* If your target platform is Android:
* macOS or Windows
* Android Studio 4.0 or higher with JDK 1.8 or higher
* An Android emulator or a physical Android device running Android SDK API 21 or higher
## Project setup [#project-setup-3]
## Implementation [#implementation-4]
## Implement peer-to-peer messaging [#implement-peer-to-peer-messaging-3]
## Test your app [#test-your-app-3]
## Next steps [#next-steps-3]
## Reference [#reference-3]
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="react-native">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="react-native" />
Instant messaging connects people wherever they are and allows them to communicate with others in real time. Agora offers an open-source Chat UI Kit project on GitHub. With built-in user interfaces for key Chat features, the Agora Chat UI Kit enables you to quickly embed real-time messaging into your app without requiring extra effort on the UI.
For the latest Agora Chat UIKit documentation, refer to the [UIKit 2.x Documentation](https://github.com/AgoraLab/agora-chat-uikit) GitHub repository.
Legacy UIkit 1.x documentation
This page shows sample code to add peer-to-peer messaging into your app by using the Agora Chat UI Kit.
## Understand the tech [#understand-the-tech-4]
The following figure shows the workflow of how clients send and receive peer-to-peer messages:
Chat UI kit workflow

1. Clients retrieve a token from your app server.
2. Client A and Client B log in to Agora Chat.
3. Client A sends a message to Client B. The message is sent to the Agora Chat server, and the server delivers the message to Client B. When Client B receives the message, the SDK triggers an event. Client B listens for the event and gets the message.
## Prerequisites [#prerequisites-4]
For more information, see [Setting up the environment](https://reactnative.dev/docs/environment-setup).
## Project setup [#project-setup-4]
## Implementation [#implementation-5]
## Implement peer-to-peer messaging [#implement-peer-to-peer-messaging-4]
## Test your app [#test-your-app-4]
## Next steps [#next-steps-4]
## Reference [#reference-4]
<_PlatformProcessedMarker close="true" />
# Chat overview (/en/realtime-media/im)
Agora's Chat API offers real-time text messaging with features like message storage, typing indicators, read receipts, and rich media support. Designed for one-to-one, group, or large-scale conversations, it’s a scalable, secure, and customizable solution for customer engagement, social platforms, or enterprise collaboration.
Use Chat as a standalone messaging solution or combine it with Voice Calling, Video Calling, Broadcast Streaming, and Interactive Live Streaming to create fully interactive, real-time experiences.
Enhance your Chat-powered app with features like offline messaging, message translation, and customizable channel and user management. Support rich media messages—including emojis, structured messages, and file sharing—to deliver engaging and seamless communication for your users.
## Start building [#start-building]
## Product features [#product-features]
Support emojis, GPS locations, structured messages, push notifications, and rich-media files with auto-generated thumbnails with Agora’s chat API service.
Integrate chat signaling, one-to-one private chat, or feature-rich group chat at scale with our chat SDK for mobile and desktop.
Allow offline messaging, message recall and delete, read receipts, presence and typing indicator, push notifications, and exporting chat history.
Build a secure chat application with TLS/SSL and file encryption and ensure data privacy compliance by allowing users to erase their personal data.
Protect users from unwanted profanity, spam, and inappropriate images or text with robust content moderation built into the chat platform.
Enable auto, on-demand, or push translation so your users can chat in their preferred language.
# Core concepts (/en/realtime-media/interactive-live-streaming/core-concepts)
RTC (Real-Time Communication) refers to real-time communication technology, which allows almost instant exchange of audio, video, and other data between the sender and the receiver.
Agora SDKs provide real-time audio and video interaction services, with multi-platform and multi-device support. This includes high-definition video calls, voice-only calls, interactive live streaming, as well as one-on-one and multi-group chats.
This guide introduces the key processes and concepts you need to know to use Video SDK.
Agora relies on the following fundamental concepts to enable seamless real-time communication:
### Agora SDRTN® [#agora-sdrtn]
Agora's core engagement services are powered by its Software-Defined Real-Time Network (SDRTN®), a global infrastructure accessible anytime, anywhere. Unlike traditional networks, Agora SDRTN® is not restricted by devices, phone numbers, or telecom coverage areas. With data centers in over 200 countries and regions, it ensures sub-second latency and high availability for real-time media.
Agora SDRTN® enables live user engagement through real-time communication (RTC), offering:
* Unmatched quality of service
* High availability and accessibility
* True scalability
* Low cost
## Channel concepts [#channel-concepts]
Agora uses channels to group users together, enabling seamless communication and interaction. Channels serve as the foundation for transmitting real-time data, whether audio, video, or signaling, and play a crucial role in connecting users and services.
### Channel [#channel]
A channel organizes users into a group and is identified by a unique channel name. Users who connect to the same channel are able to communicate with each other. A channel is created when the first user joins and ceases to exist when the last user leaves.
Channels are created by calling the methods for transmitting real-time data. Agora uses different channels to transmit different types of data:
* A Video SDK channel is used for transmitting audio or video data.
* A Signaling channel is used for transmitting messaging or signaling data.
These channels are independent of each other.
Additional services provided by Agora, such as Cloud Recording and Speech to Text, join the Video SDK channel to provide real-time recording, transmission acceleration, media playback, and content moderation.
### Channel profile [#channel-profile]
The Video SDK applies different optimization methods according to the selected channel profile. Agora supports the following channel profiles:
| Channel profile | Description |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Communication** | This profile is suitable for one-on-one or group calls, where all users in the channel talk freely. |
| **Live Broadcasting** | In a live streaming channel, users have two client roles: *host* and *audience*. The *host* sends and receives streams, while the *audience* only receives streams with the sending function disabled. |
### Stream [#stream]
A stream is a sequence of digitally encoded, coherent signals that contain media data. Users in a channel [publish](#publish) local streams and [subscribe](#subscribe) to remote streams from other users.
### User role [#user-role]
The user role defines whether a user in a channel has the permission to publish streams. There are two user roles:
* **Host**: A user who can publish streams to a channel.
* **Audience**: A User who can only subscribe to remote media streams. A user with this role cannot publish streams.
### Publish [#publish]
Publishing is the act of sending a user’s audio or video data to the channel. Usually, the published stream is created by the audio data sampled from a microphone or the video data captured by a camera. You can also publish media streams from other sources, such as an online music file or the user’s screen.
After successfully publishing a stream, the SDK uses it to send media data to other users in the channel. Users communicate with each other in real-time by publishing local streams and subscribing to remote streams.
### Subscribe [#subscribe]
Subscribing is the act of receiving media streams published by remote users to the channel. A user receives audio and video data from other users by subscribing to one or more of their streams. You either directly play the subscribed streams or process incoming data for other purposes such as recording or capturing screenshots.
### User ID [#user-id]
In Interactive Live Streaming, the UID is an integer value that uniquely identifies a user within the context of a channel. When joining a channel, you have the option to either assign a specific UID to the user or pass `0` or `null` and allow Agora to automatically generate and assign a UID to the user. If two users attempt to join the same channel with the same UID, it can lead to unexpected behavior.
The UID is used by Agora's services and components to identify and manage users within a channel. Ensure that UIDs are properly assigned to prevent conflicts.
### RTC connection [#rtc-connection]
The connection between the SDK and the channel. When publishing or subscribing to multiple streams in multiple channels, a connection is used to specify the target channel.
## Credentials [#credentials]
To ensure reliable access and secure communication, Agora uses credentials such as the App ID, App Certificate, and tokens to identify applications, authenticate their requests, and authorize their access on its platform.
### App ID [#app-id]
The App ID is a unique key generated by Agora to identify each project and provide billing and other statistical data services. The App ID is critical for connecting users within your app. It is used to initialize the Agora Engine in your app, and as one of the required keys to create authentication tokens for secure communication. Retrieve the App ID for your project using the [Agora Console](https://console.agora.io/v2/project-management).
App IDs are stored on the front-end client and do not provide access control. Projects using only an App ID allow any user with the App ID to join. For access control, especially in production environments, choose the **App ID + Token** mechanism for user authentication when creating a new project. Without authentication tokens, your environment is open to anyone with access to your App ID.
### App Certificate [#app-certificate]
An App Certificate is a unique key generated by the Agora Console to secure projects through token authentication. It is required, along with the App ID, to generate a token that proves authorization between your systems and Agora's network. App Certificates are used to generate Interactive Live Streaming authentication tokens.
Store the App Certificate securely in your backend systems. If your App Certificate is compromised or to meet security compliance requirements, you can invalidate certificates and create new ones through the Agora Console.
### Tokens [#tokens]
A token is a dynamic key generated using the App ID, App Certificate, user ID, and expiration timestamp. Tokens authenticate and secure access to Agora's services, ensuring only authorized users can join a channel and participate in real-time communication.
Tokens are generated on your server and passed to the client for use in Interactive Live Streaming. The token generation process involves digitally signing the App ID, App Certificate, user ID, and expiration timestamp using a specific algorithm, preventing tampering or forgery.
During development and testing, use the Agora Console to generate temporary tokens. For production environments, implement a token server as part of your security infrastructure to control access to your channels.
### Agora Console [#agora-console]
[Agora Console](https://console.agora.io/v2) is the main dashboard where you manage your Agora projects and services. Before you can use Agora's SDKs, you must first create a project in the Agora Console. See [Agora account management](manage-agora-account.md) for
details.

Agora Console provides an intuitive interface for developers to query and manage their Agora account. After registering an Agora account, you use the Agora Console to perform the following tasks:
* Manage your account
* Create and configure Agora projects and services
* Get an App ID and the App certificate
* Generate temporary tokens for development and testing
* Manage members and roles
* Check call quality and usage
* Check bills and make payments
* Access product resources
See [Agora account management](manage-agora-account.md) for details on how to manage all aspects of your Agora account.
Agora also provides RESTful APIs that you use to implement features such as creating a project and fetching usage numbers programmatically.
## Audio and video concepts [#audio-and-video-concepts]
### Audio and video interaction workflow [#audio-and-video-interaction-workflow]
The following figure illustrates the workflow of using the Video SDK to implement basic audio and video interaction.

Agora relies on the following fundamental concepts to enable seamless real-time communication:
### Audio module [#audio-module]
In audio interaction, the main functions of the audio module are as shown in the figure below:

After you call `registerAudioFrameObserver`, you can obtain the raw audio data at the following observation points in the audio transmission process:
1. Obtain the raw audio data of ear monitoring through the `onEarMonitoringAudioFrame` callback.
2. Obtain the captured raw audio data through the `onRecordAudioFrame` callback.
3. Obtain the raw audio playback data of each individual stream through the `onPlaybackAudioFrameBeforeMixing` callback.
4. Obtain the raw audio playback data of all mixed streams through the `onPlaybackAudioFrame` callback.
5. Obtain the raw audio data after mixing the captured and playback audio through the `onMixedAudioFrame` callback.
(5) `onMixedAudioFrame` = (2) `onRecordAudioFrame` + (4) `onPlaybackAudioFrame`
### Audio routing [#audio-routing]
The audio output device used by the app when playing audio. Common audio routes include wired headphones, earpieces, speakers, Bluetooth headphones, and others.
The APIs used by the audio module are as follows:
* Enable local audio collection: `enableLocalAudio`
* Set local playback device: `setPlaybackDevice`
* Set up audio routing: `setDefaultAudioRouteToSpeakerphone`
### Video module [#video-module]
The following diagram shows the main functions of the video module in video interaction:

The figure shows the following observation points:
1. `POSITION_POST_CAPTURER_ORIGIN`.
2. `POSITION_POST_CAPTURER`, corresponds to the `onCaptureVideoFrame` callback.
3. `POSITION_PRE_ENCODER`, corresponds to the `onPreEncodeVideoFrame` callback.
4. `POSITION_PRE_RENDERER`, corresponds to the `onRenderVideoFrame` callback.
The APIs used by the video module are as follows:
* Enable local video collection: `enableLocalVideo`
* Local preview: `setupLocalVideo` → `startPreview`
* Video rendering shows: `setupRemoteVideo`
# Agora account management (/en/realtime-media/interactive-live-streaming/manage-agora-account)
This page shows you how to sign up for an Agora account, create a new project, and get the app ID and app certificate to generate a temporary token.
## Get started with Agora [#get-started-with-agora]
To join a Interactive Live Streaming session, you need an Agora App ID. This section shows you how to set up an Agora account, create an Agora project and get the required information from [Agora Console](https://console.agora.io/v2).
### Sign up for an Agora account [#sign-up-for-an-agora-account]
To use Agora products and services, create an Agora account with your email, phone number, or a third-party account.
**Sign up with email**
1. Go to the [signup page](https://sso.agora.io/en/signup).
2. Fill in the required fields.
3. Carefully read the **Terms of Service**, **Privacy Policy**, and **Acceptable Use Policy**, and tick the checkbox.
4. Click **Continue**.
5. Enter your **verification code** and click **Confirm**.
6. Follow the on-screen instructions to provide your name, company name, and phone number, set a password, and click **Continue**.
**Sign up with a third-party account**
1. On the [Agora Console](https://console.agora.io/v2) login page, select the third-party account you want to use.
2. Follow the on-screen instructions to complete verification.
3. Click **Create a new account**.
4. Carefully read the **Terms of Service**, **Privacy Policy**, and **Acceptable Use Policy**, and select the checkbox.
5. Click **Continue**.
Once you sign up successfully, your account is automatically logged in. Follow the on-screen instructions to create your first project and test out real-time communications.
For later visits, log in to [Agora Console](https://console.agora.io/v2) with your phone number, email address, or linked third-party account.
### Create an Agora project [#create-an-agora-project]
To create an Agora project, do the following:
1. In [Agora Console](https://console.agora.io/v2), open the [Projects](https://console.agora.io/v2/project-management) page.
2. Click **Create New**.
3. Follow the on-screen instructions to enter a project name and use case, and check **Secured mode: APP ID + Token (Recommended)** as the authentication mechanism.

4. Click **Submit**. You see the new project on the **Projects** page.
### Get the App ID [#get-the-app-id]
Agora automatically assigns a unique identifier to each project, called an App ID.
To copy this App ID, find your project on the [Projects](https://console.agora.io/v2/project-management) page in Agora Console, and click the copy icon in the **App ID** column.

## Security and authentication [#security-and-authentication]
Use the following features from your Agora account to implement security and authentication features in your apps.
### Get the App Certificate [#get-the-app-certificate]
When generating an authentication token on your app server, you need an App Certificate, in addition to the App ID.
To get an App Certificate, do the following:
1. On the [Projects](https://console.agora.io/v2/project-management) page, click the pencil icon to edit the project you want to use.

2. Click the copy icon under **Primary Certificate**.

### Generate temporary tokens [#generate-temporary-tokens]
To ensure communication security, best practice is to use tokens to authenticate the users who log in from your app.
To generate a temporary RTC token for use in your Video SDK projects:
1. On the [Projects](https://console.agora.io/v2/project-management) page, click the pencil icon next to your project.
2. On the **Security** panel, click **Generate Temp Token**, enter a channel name in the pop-up box and click **Generate**. Copy the generated RTC token for use in your Interactive Live Streaming projects.
To generate a token for other Agora products:
1. In your browser, navigate to the [Agora token builder](https://agora-token-generator-demo.vercel.app/).
2. Choose the Agora product your user wants to log in to. Fill in **App ID** and **App Certificate** with the
details of your project in Agora Console.
3. Customize the token for each user. The required fields are visible in the Agora token builder.
4. Click **Generate Token**.
The token appears in Token Builder.
5. Copy the token and use it in your app.
For more information on managing other aspects of your Agora account, see [Agora console overview](reference/console-overview.md).
# Interactive Live Streaming overview (/en/realtime-media/interactive-live-streaming/product-overview)
Agora's Interactive Live Streaming API delivers low-latency, high-definition live audio and video streaming. Designed for large-scale streaming, it supports hosts, co-hosts, and audience participation, enabling engaging live experiences across platforms while maintaining reliable performance in varying network conditions. Use it for live events, webinars, online education, live commerce, gaming streams, and social live experiences where audience interaction is a core part of the experience.
Enhance Agora's Video SDK with additional capabilities such as recording, virtual backgrounds, and content moderation, or leverage the Extensions Marketplace to enable AI-powered features like noise cancellation, video effects, and more.
## Start building [#start-building]
## Product Features [#product-features]
Agora’s Software-Defined Real-Time Network (SDRTN®) supports video users in over 200 countries and regions.
Consistent high-quality video from few to thousands of concurrent users, even under challenging network conditions.
Publish multiple audio and video tracks to one or more channels from a single instance, with support for multi-channel capture cameras and microphones.
Enable screen sharing or interactive whiteboards that allow users to draw, annotate, and share content from multiple devices simultaneously.
Support for high quality audio with 3D spatial audio, AI noise suppression, and gain control to provide an immersive audio experience.
Record video sessions in the cloud or on premises with control over the format, path of storage, and quality.
# Quickstart (/en/realtime-media/interactive-live-streaming/quickstart)
<_PlatformTabsGroup groupMode="structured" canonicalPlatform="web" platforms="["android","ios","macos","web","windows","electron","flutter","react-native","javascript","unity","unreal","blueprint","python"]" 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 Interactive Live Streaming app using the Agora Video SDK.
## Understand the tech [#understand-the-tech]
To start a Interactive Live Streaming session, implement the following steps in your app:
* **Initialize the Agora Engine**: Before calling other APIs, create and initialize an Agora Engine instance.
* **Join a channel**: Call methods to create and join a channel.
* **Join as a host**: A live streaming event has one or more hosts. A host publishes audio and video to the channel. Hosts can also subscribe to streams from other hosts.
* **Join as audience**: Audience members can only subscribe to streams published by hosts.
* **Send and receive audio and video**: Hosts publish streams to the channel. Audience members subscribe to audio and video streams published by hosts.

## Prerequisites [#prerequisites]
* [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.
* A camera and a microphone
* A valid Agora account and project. Please refer to [Agora account management](manage-agora-account.md) for details.
## Set up your project [#set-up-your-project]
This section shows you how to set up your Android project and install the Agora Video SDK.
**Create a new project**
1. Create a [new project](https://developer.android.com/studio/projects/create-project).
1. Open Android Studio and select **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.
After you create a project, Android Studio automatically starts gradle sync. Ensure that the synchronization is successful before proceeding to the next step.
**Add to an existing project**
1. Add a new activity to your project.
1. Open your project in Android Studio.
2. Right-click on the `app/src/main/java/` 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.
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.
**Maven Central**
1. Open the `settings.gradle` file in the project's root directory and add the Maven Central dependency, if it doesn't already exist:
```text
repositories {
mavenCentral()
}
```
If your Android project uses dependencyResolutionManagement, the method of adding the Maven Central dependency may differ.
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`
```json
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`.
To get the latest version number, check the [Release notes](reference/release-notes). To integrate the Lite SDK, use `io.agora.rtc:lite-sdk` instead.
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.**
```
**Manual integration**
1. Download the latest version of Video SDK from the [SDKs](/en/api-reference/sdks?product=video\&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.**
```
## Implement Interactive Live Streaming [#implement-interactive-live-streaming]
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:

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:
Java
Kotlin
```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;
```
```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
```
### 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](manage-agora-account.md), and custom [event handler](#subscribe-to--events), then call `RtcEngine.create(config)` to initialize the engine, enabling further channel operations. In your `MainActivity` file, add the following code:
Java
Kotlin
```java
// Fill in the app ID from Agora Console
private String myAppId = "";
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());
}
}
```
```kotlin
// Fill in the App ID obtained from the Agora Console
private val myAppId = ""
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}")
}
}
```
### 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](build/authenticate-users/deploy-token-server.mdx) in your security infrastructure. For the purpose of this guide [Generate a temporary token](manage-agora-account.md).
* **User ID**: A 32-bit signed integer that identifies a user in the channel. You can specify a unique user ID for each user yourself. If you set the user ID to `0` when joining a channel, the SDK generates a random number for the user ID and returns the value in the `onJoinChannelSuccess` callback.
* **Channel media options**: Configure `ChannelMediaOptions` to define publishing and subscription settings, optimize performance for your specific use-case, and set optional parameters.
For Interactive Live Streaming, set the `channelProfile` to `CHANNEL_PROFILE_LIVE_BROADCASTING`, the `clientRoleType` to `CLIENT_ROLE_BROADCASTER` (host) or `CLIENT_ROLE_AUDIENCE`, and the `audienceLatencyLevel` to `AUDIENCE_LATENCY_LEVEL_ULTRA_LOW_LATENCY`.
Java
Kotlin
```java
// Fill in the channel name
private String channelName = "";
// Fill in the temporary token generated from Agora Console
private String token = "";
private void joinChannel() {
// Create an instance of ChannelMediaOptions and configure it
ChannelMediaOptions options = new ChannelMediaOptions();
// Set the user role to BROADCASTER or AUDIENCE according to the use-case
options.clientRoleType = Constants.CLIENT_ROLE_BROADCASTER;
// In the live broadcast use-case, set the channelProfile to BROADCASTING (live broadcast use-case)
options.channelProfile = Constants.CHANNEL_PROFILE_LIVE_BROADCASTING;
// Set the latency level for audience
options.audienceLatencyLevel = Constants.AUDIENCE_LATENCY_LEVEL_ULTRA_LOW_LATENCY;
// Publish local media
options.publishCameraTrack = true;
options.publishMicrophoneTrack = true;
mRtcEngine.joinChannel(token, channelName, 0, options);
}
```
```kotlin
// Fill in the channel name
private val channelName = ""
// Fill in the temporary token generated from Agora Console
private val token = ""
private fun joinChannel() {
// Create an instance of ChannelMediaOptions and configure it
val options = ChannelMediaOptions().apply {
// Set the user role to BROADCASTER or AUDIENCE according to the use-case
clientRoleType = Constants.CLIENT_ROLE_BROADCASTER
// In the live broadcast use-case, set the channelProfile to BROADCASTING (live broadcast use-case)
channelProfile = Constants.CHANNEL_PROFILE_LIVE_BROADCASTING
// Set the latency level for audience
audienceLatencyLevel = Constants.AUDIENCE_LATENCY_LEVEL_ULTRA_LOW_LATENCY
// Publish local media
publishMicrophoneTrack = true
publishCameraTrack = true
}
mRtcEngine.joinChannel(token, channelName, 0, options)
}
```
### 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.
To ensure that you receive all Video SDK events, set the Agora Engine event handler before joining a channel.
Java
Kotlin
```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);
});
}
};
```
```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")
}
}
}
```
### 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.
Java
Kotlin
```java
private void enableVideo() {
mRtcEngine.enableVideo();
mRtcEngine.startPreview();
}
```
```kotlin
private fun enableVideo() {
mRtcEngine?.apply {
enableVideo()
startPreview()
}
}
```
### Display the local video [#display-the-local-video]
Call `setupLocalVideo` to initialize the local view and set the local video display properties.
Java
Kotlin
```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));
}
```
```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))
}
```
### 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.
Java
Kotlin
```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));
}
```
```kotlin
private fun setupRemoteVideo(uid: Int) {
val container = findViewById(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))
}
```
### Handle permissions [#handle-permissions]
To access the camera and microphone on Android devices, declare the necessary permissions in the app's manifest and ensure that the user grants these permissions when the app starts.
1. Open your project's `AndroidManifest.xml` file and add the following permissions before ``:
```xml
```
2. Use the following code to handle runtime permissions in your Android app. The logic ensures that the necessary permissions are granted before starting Interactive Live Streaming. In your `MainActivity` file, add the following code:
Java
Kotlin
```java
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()) {
startLiveStreaming();
}
}private boolean checkPermissions() {
```
```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 {
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,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == PERMISSION_REQ_ID && checkPermissions()) {
startLiveStreaming()
}
}
```
### Start and close the app [#start-and-close-the-app]
When a user launches your app, start real-time interaction. When a user closes the app, stop the interaction.
1. In the `onCreate` callback, check whether the app 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.
Java
Kotlin
```java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (checkPermissions()) {
startLiveStreaming();
} else {
requestPermissions();
}
}
```
```kotlin
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (checkPermissions()) {
startLiveStreaming()
} else {
requestPermissions()
}
}
```
2. When a user closes the app, or switches the app 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.
Java
Kotlin
```java
private void cleanupAgoraEngine() {
if (mRtcEngine != null) {
mRtcEngine.stopPreview();
mRtcEngine.leaveChannel();
mRtcEngine = null;
}
}
```
```kotlin
private fun cleanupAgoraEngine() {
mRtcEngine?.apply {
stopPreview()
leaveChannel()
}
mRtcEngine = null
}
```
### 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 `` in package `com.example.` with your project's name.
Java
Kotlin
```java
package com.example.
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;
// Fill in the app ID from Agora Console
private String myAppId = "";
// Fill in the channel name
private String channelName = "";
// Fill in the temporary token generated from Agora Console
private String 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()) {
startLiveStreaming();
} 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()) {
startLiveStreaming();
}
}
private void startLiveStreaming() {
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() {
// Create an instance of ChannelMediaOptions and configure it
ChannelMediaOptions options = new ChannelMediaOptions();
// Set the user role to BROADCASTER or AUDIENCE according to the use-case
options.clientRoleType = Constants.CLIENT_ROLE_BROADCASTER;
// In the live broadcast use-case, set the channel profile to BROADCASTING (live broadcast use-case)
options.channelProfile = Constants.CHANNEL_PROFILE_LIVE_BROADCASTING;
// Set the audience latency level
options.audienceLatencyLevel = Constants.AUDIENCE_LATENCY_LEVEL_ULTRA_LOW_LATENCY;
// Publish local media
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());
}
}
```
```kotlin
package com.example.
import android.Manifest
import android.content.pm.PackageManager
import android.os.Build
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() {
private val PERMISSION_REQ_ID = 22
private val myAppId = ""
private val channelName = ""
private val 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)
runOnUiThread {
showToast("Joined channel $channel")
}
}
override fun onUserJoined(uid: Int, elapsed: Int) {
runOnUiThread {
setupRemoteVideo(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()) {
startLiveStreaming()
} else {
requestPermissions()
}
}
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 {
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,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == PERMISSION_REQ_ID && checkPermissions()) {
startLiveStreaming()
}
}
private fun startLiveStreaming() {
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 = findViewById(R.id.local_video_view_container)
val surfaceView = SurfaceView(this)
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_LIVE_BROADCASTING
audienceLatencyLevel = Constants.AUDIENCE_LATENCY_LEVEL_ULTRA_LOW_LATENCY
publishMicrophoneTrack = true
publishCameraTrack = true
}
mRtcEngine?.joinChannel(token, channelName, 0, options)
}
private fun setupRemoteVideo(uid: Int) {
val container = findViewById(R.id.remote_video_view_container)
val surfaceView = SurfaceView(this).apply {
setZOrderMediaOverlay(true)
}
container.addView(surfaceView)
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, message, Toast.LENGTH_SHORT).show()
}
}
}
```
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.
### 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.

**Sample code to create the user interface**
```xml
```
## 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  **Sync Project with Gradle Files** to resolve project dependencies and update the configuration.
4. After synchronization is successful, click  **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 app. 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 to this product.
* If a firewall is deployed in your network environment, refer to [Connect with Cloud Proxy](build/optimize-quality-and-connection/cloud-proxy.mdx) to use Agora services normally.
### Next steps [#next-steps]
After implementing the quickstart sample, read the following documents to learn more:
* To ensure communication security in a test or production environment, best practice is to obtain and use a token from an authentication server. For details, see [Secure authentication with tokens](build/authenticate-users/use-tokens.mdx).
### Sample project [#sample-project]
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)
* [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]
* [Error codes](/en/realtime-media/video/reference/error-codes)
* [Connection status management](build/optimize-quality-and-connection/connection-status-management.mdx)
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="ios">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="ios" />
This page provides a step-by-step guide on how to create a basic Interactive Live Streaming app using the Agora Video SDK.
## Understand the tech [#understand-the-tech-1]
To start a Interactive Live Streaming session, implement the following steps in your app:
* **Initialize the Agora Engine**: Before calling other APIs, create and initialize an Agora Engine instance.
* **Join a channel**: Call methods to create and join a channel.
* **Join as a host**: A live streaming event has one or more hosts. A host publishes audio and video to the channel. Hosts can also subscribe to streams from other hosts.
* **Join as audience**: Audience members can only subscribe to streams published by hosts.
* **Send and receive audio and video**: Hosts publish streams to the channel. Audience members subscribe to audio and video streams published by hosts.

## Prerequisites [#prerequisites-1]
* Xcode 13.0 or higher.
* An Apple developer account.
* If you need to use CocoaPods integrated SDK, make sure [CocoaPods is installed](https://guides.cocoapods.org/using/getting-started.html#getting-started).
* Two devices running iOS 14.0 or higher.
* A camera and a microphone
* A valid Agora account and project. Please refer to [Agora account management](manage-agora-account.md) for details.
## Set up your project [#set-up-your-project-1]
This section shows you how to set up your iOS project and install the Agora Video SDK.
**Create a new project**
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.
If you have not added the development team information, you see the **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.
2. [Set up automatic signing](https://help.apple.com/xcode/mac/current/#/dev23aab79b4) for your projects.
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.
**Add to an existing project**
Follow these steps to add Interactive Live Streaming 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.
### Install the SDK [#install-the-sdk-1]
Use one of the following methods to install Video SDK.
**Swift Package Manager**
1. In Xcode, go to **File** > **Add Package Dependencies**.
2. In the search bar, past the following URL:
```json
{`https://github.com/AgoraIO/AgoraRtcEngine_iOS.git`}
```
3. Click **Add Package**, select the latest version, and click **Next**.
4. For basic Interactive Live Streaming, 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 further information, refer to [Apple's official documentation](https://help.apple.com/xcode/mac/current/#/devb83d64851).
**CocoaPods**
1. Go to the project root directory in the terminal and run the `pod init` command. 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
# For x.y.z fill in 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
```
Obtain the latest version number from the [release notes](reference/release-notes).
3. Run the `pod install` command in the terminal to install the Video SDK. After successful installation, the terminal shows **Pod installation complete!**.
4. After successful installation, a file with the suffix `.xcworkspace` is generated in the project folder. Open the file through Xcode for subsequent operations.
**Manual integration**
1. Download the latest version of the SDK from [SDKs download](/en/api-reference/sdks?product=video\&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**.
Agora SDK uses `libc++` (LLVM) by default. If you need to use `libstdc++` (GNU), please contact [support@agora.io](mailto\:support@agora.io). The library provided by the SDK is a FAT Image, which includes 32/64-bit simulator and 32/64-bit real machine versions.
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.
## Implement Interactive Live Streaming [#implement-interactive-live-streaming-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:

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 AgoraRtcKit
```
### Initialize the engine [#initialize-the-engine-1]
Call `sharedEngine(withAppId:delegate:)` to create and initialize an `AgoraRtcEngineKit` instance. Provide your [App ID](manage-agora-account.md) and an `AgoraRtcEngineDelegate` implementation to [handle SDK events](#subscribe-to--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](build/authenticate-users/deploy-token-server.mdx) in your security infrastructure. For the purpose of this guide [Generate a temporary token](manage-agora-account.md).
* **User ID**: A 32-bit signed integer that identifies a user in the channel. You can specify a unique user ID for each user yourself. If you set the user ID to `0` when joining a channel, the SDK generates a random number for the user ID and returns the value in the `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 Interactive Live Streaming, set the `channelProfile` to `.liveBroadcasting`, the `clientRoleType` to `.broadcaster` (host) or `.audience`, and the `audienceLatencyLevel` to `ultraLowLatency`.
```swift
let channelName = "demo" // Replace with your actual channel name
let token = "" // Replace with your token
// Join the channel with specified options
func joinChannel() {
let options = AgoraRtcChannelMediaOptions()
// In a live streaming use-case, set the channel use-case to liveBroadcasting
options.channelProfile = .liveBroadcasting
// 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
// Set the audience latency level
options.audienceLatencyLevel = .ultraLowLatency
// Use a temporary Token to join the channel
agoraKit.joinChannel(
byToken: token,
channelId: channelName,
uid: 0,
mediaOptions: options
)
}
```
You can also set the latency level after joining a channel by calling the `setClientRole` method with `role` set to `AgoraClientRole.audience` and `options.audienceLatencyLevel` set to `ultraLowLatency`.
```swift
// Set the user role to audience
let role = AgoraClientRole.audience
let options = AgoraClientRoleOptions()
// Set the ultra low latency level
options.audienceLatencyLevel = .ultraLowLatency
agoraKit.setClientRole(role, options: 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:
To ensure that you receive all Video SDK events, set the Agora Engine event handler before joining a channel.
```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 iOS 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. |
* 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).
### Start and close the app [#start-and-close-the-app-1]
When the user launches the app, it joins the channel and starts Interactive Live Streaming. When the user closes the app, it leaves the channel and ends Interactive Live Streaming.
1. To start Interactive Live Streaming, 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()
```
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.
### 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:
```swift
// ViewController.swift
import UIKit
import AgoraRtcKit
class ViewController: UIViewController {
let appId = "" // Replace with your actual App ID
let channelName = "demo" // Replace with your actual channel name
let 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 a live streaming use-case, set the channel use-case to liveBroadcasting
options.channelProfile = .liveBroadcasting
// 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
// Set the audience ultra-low latency level
options.audienceLatencyLevel = .ultraLowLatency
// 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) {
// 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)
}
}
```
### 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**
```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 iOS device, repeat the previous steps to install and launch the app. 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 to this product.
* If a firewall is deployed in your network environment, refer to [Connect with Cloud Proxy](build/optimize-quality-and-connection/cloud-proxy.mdx) to use Agora services normally.
### Next steps [#next-steps-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](build/authenticate-users/use-tokens.mdx).
### 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 iOS 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:
2. Choose **File > New File**.
3. Scroll down to the **Resource** section and select **App Privacy File** type.
4. Click **Next**.
5. Check your app in the **Targets** list.
6. 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 `PrivacyInfo.xcprivacy` file of the Video SDK to `PrivacyInfo.xcprivacy` of the app using the following source code:
```xml
NSPrivacyTrackingNSPrivacyCollectedDataTypesNSPrivacyAccessedAPITypesNSPrivacyAccessedAPITypeNSPrivacyAccessedAPICategorySystemBootTimeNSPrivacyAccessedAPITypeReasons35F9.1NSPrivacyAccessedAPITypeNSPrivacyAccessedAPICategoryFileTimestampNSPrivacyAccessedAPITypeReasonsDDA9.1NSPrivacyAccessedAPITypeNSPrivacyAccessedAPICategoryDiskSpaceNSPrivacyAccessedAPITypeReasonsE174.1
```
### 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\:info\:uid\: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)
* [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)
* [What can I do if I get a pop-up warning saying 'the framework cannot be opened' when compiling an Xcode project?](/en/api-reference/faq/integration/framework_cannot_be_opened)
* [How can I add a privacy manifest to my iOS app?](/en/api-reference/faq/other/ios_privacy_manifest)
### See also [#see-also-1]
* [Error codes](/en/realtime-media/video/reference/error-codes)
* [Connection status management](build/optimize-quality-and-connection/connection-status-management.mdx)
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="macos">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="macos" />
This page provides a step-by-step guide on how to create a basic Interactive Live Streaming app using the Agora Video SDK.
## Understand the tech [#understand-the-tech-2]
To start a Interactive Live Streaming session, implement the following steps in your app:
* **Initialize the Agora Engine**: Before calling other APIs, create and initialize an Agora Engine instance.
* **Join a channel**: Call methods to create and join a channel.
* **Join as a host**: A live streaming event has one or more hosts. A host publishes audio and video to the channel. Hosts can also subscribe to streams from other hosts.
* **Join as audience**: Audience members can only subscribe to streams published by hosts.
* **Send and receive audio and video**: Hosts publish streams to the channel. Audience members subscribe to audio and video streams published by hosts.

## Prerequisites [#prerequisites-2]
* Xcode 13.0 or higher.
* An Apple developer account.
* If you need to use CocoaPods integrated SDK, make sure [CocoaPods is installed](https://guides.cocoapods.org/using/getting-started.html#getting-started).
* Two devices running macOS 10.10 or higher.
* A camera and a microphone
* A valid Agora account and project. Please refer to [Agora account management](manage-agora-account.md) for details.
## Set up your project [#set-up-your-project-2]
This section shows you how to set up your macOS project and install the Agora Video SDK.
**Create a new project**
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.
If you have not added the development team information, you see the **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.
2. [Set up automatic signing](https://help.apple.com/xcode/mac/current/#/dev23aab79b4) for your projects.
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.
**Add to an existing project**
Follow these steps to add Interactive Live Streaming 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.
### Install the SDK [#install-the-sdk-2]
Use one of the following methods to install Video SDK.
**Swift Package Manager**
1. In Xcode, go to **File** > **Add Package Dependencies**.
2. In the search bar, past the following URL:
```json
{`https://github.com/AgoraIO/AgoraRtcEngine_macOS.git`}
```
3. Click **Add Package**, select the latest version, and click **Next**.
4. For basic Interactive Live Streaming, 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 further information, refer to [Apple's official documentation](https://help.apple.com/xcode/mac/current/#/devb83d64851).
**CocoaPods**
1. Go to the project root directory in the terminal and run the `pod init` command. 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
# For x.y.z fill in the specific SDK version number, such as 4.4.0.
pod 'AgoraRtcEngine_macOS', 'x.y.z'
end
```
Obtain the latest version number from the [release notes](reference/release-notes).
3. Run the `pod install` command in the terminal to install the Video SDK. After successful installation, the terminal shows **Pod installation complete!**.
4. After successful installation, a file with the suffix `.xcworkspace` is generated in the project folder. Open the file through Xcode for subsequent operations.
**Manual integration**
1. Download the latest version of the SDK from [SDKs download](/en/api-reference/sdks?product=video\&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**.
Agora SDK uses `libc++` (LLVM) by default. If you need to use `libstdc++` (GNU), please contact [support@agora.io](mailto\:support@agora.io). The library provided by the SDK is a FAT Image, which includes 32/64-bit simulator and 32/64-bit real machine versions.
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.
## Implement Interactive Live Streaming [#implement-interactive-live-streaming-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:

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 AgoraRtcKit
```
### Initialize the engine [#initialize-the-engine-2]
Call `sharedEngine(withAppId:delegate:)` to create and initialize an `AgoraRtcEngineKit` instance. Provide your [App ID](manage-agora-account.md) and an `AgoraRtcEngineDelegate` implementation to [handle SDK events](#subscribe-to--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](build/authenticate-users/deploy-token-server.mdx) in your security infrastructure. For the purpose of this guide [Generate a temporary token](manage-agora-account.md).
* **User ID**: A 32-bit signed integer that identifies a user in the channel. You can specify a unique user ID for each user yourself. If you set the user ID to `0` when joining a channel, the SDK generates a random number for the user ID and returns the value in the `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 Interactive Live Streaming, set the `channelProfile` to `.liveBroadcasting`, the `clientRoleType` to `.broadcaster` (host) or `.audience`, and the `audienceLatencyLevel` to `ultraLowLatency`.
```swift
let channelName = "demo" // Replace with your actual channel name
let token = "" // Replace with your token
// Join the channel with specified options
func joinChannel() {
let options = AgoraRtcChannelMediaOptions()
// In a live streaming use-case, set the channel use-case to liveBroadcasting
options.channelProfile = .liveBroadcasting
// 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
// Set the audience latency level
options.audienceLatencyLevel = .ultraLowLatency
// Use a temporary Token to join the channel
agoraKit.joinChannel(
byToken: token,
channelId: channelName,
uid: 0,
mediaOptions: options
)
}
```
You can also set the latency level after joining a channel by calling the `setClientRole` method with `role` set to `AgoraClientRole.audience` and `options.audienceLatencyLevel` set to `ultraLowLatency`.
```swift
// Set the user role to audience
let role = AgoraClientRole.audience
let options = AgoraClientRoleOptions()
// Set the ultra low latency level
options.audienceLatencyLevel = .ultraLowLatency
agoraKit.setClientRole(role, options: 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:
To ensure that you receive all Video SDK events, set the Agora Engine event handler before joining a channel.
```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 macOS 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. |
* 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).
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 Interactive Live Streaming. When the user closes the app, it leaves the channel and ends Interactive Live Streaming.
1. To start Interactive Live Streaming, 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()
```
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.
### 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:
```swift
// ViewController.swift
import Cocoa
import AgoraRtcKit
class ViewController: NSViewController {
let appId = "" // Replace with your actual App ID
let channelName = "demo" // Replace with your actual channel name
let 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 a live streaming use-case, set the channel use-case to liveBroadcasting
options.channelProfile = .liveBroadcasting
// 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
// Set the audience latency level
options.audienceLatencyLevel = .ultraLowLatency
// Use a temporary Token to join the channel
// If you set uid=0, the engine generates a uid internally; on success, it triggers didJoinChannel callback
agoraKit.joinChannel(byToken: "<#Your Token#>", channelId: "<#Your Channel Name#>", 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)
}
}
```
### 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**
```swift
// ViewController.swift
import Cocoa
import AgoraRtcKit
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 macOS device, repeat the previous steps to install and launch the app. 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 to this product.
* If a firewall is deployed in your network environment, refer to [Connect with Cloud Proxy](build/optimize-quality-and-connection/cloud-proxy.mdx) to use Agora services normally.
### Next steps [#next-steps-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](build/authenticate-users/use-tokens.mdx).
### 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\:info\:uid\: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)
### See also [#see-also-2]
* [Error codes](/en/realtime-media/video/reference/error-codes)
* [Connection status management](build/optimize-quality-and-connection/connection-status-management.mdx)
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="web">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="web" />
Explore sample implementations to quickly integrate Conversational AI.
* [RTC SDK API examples](https://github.com/AgoraIO/API-Examples-Web): Sample projects for the Agora RTC Web SDK 4.x, covering basic, advanced examples for Vue and React.
This page provides a step-by-step guide on how to create a basic Interactive Live Streaming app using the Agora Video SDK.
## Understand the tech [#understand-the-tech-3]
To start a Interactive Live Streaming session, implement the following steps in your app:
* **Initialize the Agora Engine**: Before calling other APIs, create and initialize an Agora Engine instance.
* **Join a channel**: Call methods to create and join a channel.
* **Join as a host**: A live streaming event has one or more hosts. A host publishes audio and video to the channel. Hosts can also subscribe to streams from other hosts.
* **Join as audience**: Audience members can only subscribe to streams published by hosts.
* **Send and receive audio and video**: Hosts publish streams to the channel. Audience members subscribe to audio and video streams published by hosts.

## Prerequisites [#prerequisites-3]
* A [supported browser](/en/realtime-media/interactive-live-streaming/reference/supported-platforms).
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)
* A camera and a microphone
* A valid Agora account and project. Please refer to [Agora account management](manage-agora-account.md) for details.
## Set up your project [#set-up-your-project-3]
This section shows you how to set up your Web project and install the Agora Video SDK.
**Create a new project**
To initialize a new Vite project, take the following steps:
1. Open a terminal and run the following command:
```bash
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.
1. Navigate to the newly created folder. Download and set up dependencies for your project:
```bash
cd agora_web_quickstart
npm install
```
2. 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.
**Add to an existing project**
To add Interactive Live Streaming to your existing project, take the following steps:
2. Create a JavaScript file in your project's `src` folder to add `AgoraRTCClient` code that implements specific application logic.
3. 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.
4. Include the JavaScript file in your HTML file.
```html
```
### Install the SDK [#install-the-sdk-3]
Add the Video SDK to your project:
```bash
npm install agora-rtc-sdk-ng
```
## Implement Interactive Live Streaming [#implement-interactive-live-streaming-3]
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 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 Interactive Live Streaming Set `mode` to `live`.
```javascript
// RTC client instance
let client = null;
// Initialize the AgoraRTC client
function initializeClient() {
client = AgoraRTC.createClient({ mode: "live", codec: "vp8", role: "host" });
setupEventListeners();
}
```
### Join a channel [#join-a-channel-3]
To join a channel, call `join` with the following parameters:
* **App ID**: The [App ID](manage-agora-account.md) 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](build/authenticate-users/deploy-token-server.mdx) in your security infrastructure. For the purpose of this guide [Generate a temporary token](manage-agora-account.md).
* **User ID**: A 32-bit signed integer that identifies a user in the channel. You can specify a unique user ID for each user yourself. If you set the user ID to `0` when joining a channel, the SDK generates a random number for the user ID.
When you join as a host, you can publish local media streams in the channel; you can also subscribe to streams published by other hosts. As an audience member, you may only subscribe to media streams published by hosts.
* Join as a host
```js
// Join as a host
async function joinAsHost() {
await client.join(appId, channel, token, uid);
// A host can both publish tracks and subscribe to tracks
client.setClientRole("host");
// Create and publish local tracks
await createLocalMediaTracks();
await publishLocalTracks();
displayLocalVideo();
}
```
* Join as audience
To join a channel as an audience member, first call `join`, then use `setClientRole` to set the role. Listen for the `user-published` callback to play the tracks published in the channel.
```js
// Join as audience
async function joinAsAudience() {
await client.join(appId, channel, token, uid);
// Set ultra-low latency level for interactive live streaming
let clientRoleOptions = { level: 2 };
// Audience can only subscribe to tracks
client.setClientRole("audience", clientRoleOptions);
}
```
### 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--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("");
}
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`.
To ensure that you receive all Video SDK events, set up event listeners before joining a channel.
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.
```js
import AgoraRTC from "agora-rtc-sdk-ng";
// RTC client instance
let client = null;
// Declare variables for 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: "live", codec: "vp8", role: "host" });
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", async (user) => {
const remotePlayerContainer = document.getElementById(user.uid);
remotePlayerContainer && remotePlayerContainer.remove();
});
}
// Create and publish local tracks
async function createLocalTracks() {
localAudioTrack = await AgoraRTC.createMicrophoneAudioTrack();
localVideoTrack = await AgoraRTC.createCameraVideoTrack();
}
// 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);
}
// Join as a host
async function joinAsHost() {
await client.join(appId, channel, token, uid);
// A host can both publish tracks and subscribe to tracks
client.setClientRole("host");
// Create and publish local tracks
await createLocalTracks();
await publishLocalTracks();
displayLocalVideo();
disableJoinButtons();
console.log("Host joined and published tracks.");
}
// Join as audience
async function joinAsAudience() {
await client.join(appId, channel, token, uid);
// Set ultra-low latency level
let clientRoleOptions = { level: 2 };
// Audience can only subscribe to tracks
client.setClientRole("audience", clientRoleOptions);
disableJoinButtons();
console.log("Audience joined.");
}
// Publish local tracks
async function publishLocalTracks() {
await client.publish([localAudioTrack, localVideoTrack]);
}
// 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
async function leaveChannel() {
if (localAudioTrack) {
localAudioTrack.close();
localAudioTrack = null;
}
if (localVideoTrack) {
localVideoTrack.close();
localVideoTrack = null;
}
const localPlayerContainer = document.getElementById(uid);
localPlayerContainer && localPlayerContainer.remove();
client.remoteUsers.forEach((user) => {
const playerContainer = document.getElementById(user.uid);
playerContainer && playerContainer.remove();
});
await client.leave();
enableJoinButtons();
console.log("Left the channel.");
}
// Disable join buttons
function disableJoinButtons() {
document.getElementById("host-join").disabled = true;
document.getElementById("audience-join").disabled = true;
}
// Enable join buttons
function enableJoinButtons() {
document.getElementById("host-join").disabled = false;
document.getElementById("audience-join").disabled = false;
}
// Set up event listeners for buttons
function setupButtonHandlers() {
document.getElementById("host-join").onclick = joinAsHost;
document.getElementById("audience-join").onclick = joinAsAudience;
document.getElementById("leave").onclick = leaveChannel;
}
// Start live streaming
function startBasicLiveStreaming() {
initializeClient();
window.onload = setupButtonHandlers;
}
startBasicLiveStreaming();
```
### 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**
```html
Agora Web SDK Quickstart
Agora Web SDK Quickstart
Join as hostJoin as audienceLeave
```
## 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:
```bash
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:

4. On a second device, repeat the previous steps to install and launch the app. 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:
* 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; the audience can see the host in the remote video view and hear the host.
* 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`).
## Reference [#reference-3]
This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.
* If a firewall is deployed in your network environment, refer to [Connect with Cloud Proxy](build/optimize-quality-and-connection/cloud-proxy.mdx) to use Agora services normally.
### Next steps [#next-steps-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](build/authenticate-users/use-tokens.mdx).
### 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+) |
* 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.
### 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:

### 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](build/optimize-quality-and-connection/app-size-optimization.mdx).
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
```
* 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
```
Visit the [Download](/en/api-reference/sdks?product=video\&platform=web) page to obtain the link for the latest SDK version.
### Frequently asked questions [#frequently-asked-questions-2]
**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):
```bash
export NODE_OPTIONS=--openssl-legacy-provider
```
* Temporarily switch to a lower version of Node.js.
### See also [#see-also-3]
* [Error codes](/en/realtime-media/video/reference/error-codes)
* [Connection status management](build/optimize-quality-and-connection/connection-status-management.mdx)
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="windows">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="windows" />
This page provides a step-by-step guide on how to create a basic Interactive Live Streaming app using the Agora Video SDK.
## Understand the tech [#understand-the-tech-4]
To start a Interactive Live Streaming session, implement the following steps in your app:
* **Initialize the Agora Engine**: Before calling other APIs, create and initialize an Agora Engine instance.
* **Join a channel**: Call methods to create and join a channel.
* **Join as a host**: A live streaming event has one or more hosts. A host publishes audio and video to the channel. Hosts can also subscribe to streams from other hosts.
* **Join as audience**: Audience members can only subscribe to streams published by hosts.
* **Send and receive audio and video**: Hosts publish streams to the channel. Audience members subscribe to audio and video streams published by hosts.

## Prerequisites [#prerequisites-4]
* 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. 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).
* A camera and a microphone
* A valid Agora account and project. Please refer to [Agora account management](manage-agora-account.md) for details.
## Set up your project [#set-up-your-project-4]
This section shows you how to set up your Windows project and install the Agora Video SDK.
**Create a new project**
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**.
**Add to an existing project**
To integrate real-time audio and video interaction into your project:
3. Launch Visual Studio 2022 and open your existing project by selecting **File > Open > Project/Solution**.
4. Navigate to your project directory and open the `.sln` file.
5. 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?product=video\&platform=windows).
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 Interactive Live Streaming [#implement-interactive-live-streaming-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:

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](manage-agora-account.md), and custom [event handler](#subscribe-to--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](build/authenticate-users/deploy-token-server.mdx) in your security infrastructure. For the purpose of this guide [Generate a temporary token](manage-agora-account.md).
* **User ID**: A 32-bit signed integer that identifies a user in the channel. You can specify a unique user ID for each user yourself. If you set the user ID to `0` when joining a channel, the SDK generates a random number for the user ID and returns the value in the `onJoinChannelSuccess` callback.
* **Channel media options**: Configure `ChannelMediaOptions` to define publishing and subscription settings, optimize performance for your specific use-case, and set optional parameters.
For Interactive Live Streaming, set the `channelProfile` to `CHANNEL_PROFILE_LIVE_BROADCASTING`, the `clientRoleType` to `CLIENT_ROLE_BROADCASTER` (host) or `CLIENT_ROLE_AUDIENCE`, and the `audienceLatencyLevel` to `AUDIENCE_LATENCY_LEVEL_ULTRA_LOW_LATENCY`.
```cpp
void CAgoraQuickStartDlg::joinChannel(const char* token, const char* channelName) {
ChannelMediaOptions options;
// Set channel profile to live broadcasting
options.channelProfile = CHANNEL_PROFILE_LIVE_BROADCASTING;
// Set user role to broadcaster; keep default value if setting user role to audience
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;
// Specify the audio latency level
options.audienceLatencyLevel = AUDIENCE_LATENCY_LEVEL_ULTRA_LOW_LATENCY;
// Join the channel using the token and channel name (both are const char*)
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.
To ensure that you receive all Video SDK events, set the Agora Engine event handler before joining a channel.
```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:
2. Set the video rendering mode.
3. Specify the user ID (`uid`).
4. Define the display window.
5. Call the `setupLocalVideo` method to apply the `VideoCanvas` configuration.
6. 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 app 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;
```
After you call `Dispose`, you can no longer use any SDK methods or callbacks. To use real-time Interactive Live Streaming again, you must create a new engine. For more information, see [Initialize the Engine](#initialize-the-engine).
### 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**
```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**
```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() {
if (m_rtcEngine) {
m_rtcEngine->release(true);
m_rtcEngine = NULL;
}
}
void CAgoraQuickStartDlg::DoDataExchange(CDataExchange* pDX) {
CDialog::DoDataExchange(pDX);
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)
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()
// Insert your project's App ID obtained from the Agora Console
#define APP_ID ""
// Insert the temporary token obtained from the Agora Console
#define token ""
void CAgoraQuickStartDlg::initializeAgoraEngine() {
m_rtcEngine = createAgoraRtcEngine();
RtcEngineContext context;
context.appId = APP_ID;
context.eventHandler = &m_eventHandler;
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"));
}
}
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);
}
}
void CAgoraQuickStartDlg::OnPaint() {
if (IsIconic()) {
CPaintDC dc(this);
SendMessage(WM_ICONERASEBKGND, reinterpret_cast(dc.GetSafeHdc()), 0);
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;
dc.DrawIcon(x, y, m_hIcon);
} else {
CDialog::OnPaint();
}
}
HCURSOR CAgoraQuickStartDlg::OnQueryDragIcon() {
return static_cast(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;
options.channelProfile = CHANNEL_PROFILE_LIVE_BROADCASTING;
options.clientRoleType = CLIENT_ROLE_BROADCASTER;
options.publishMicrophoneTrack = true;
options.publishCameraTrack = true;
options.autoSubscribeAudio = true;
options.autoSubscribeVideo = true;
options.audienceLatencyLevel = AUDIENCE_LATENCY_LEVEL_ULTRA_LOW_LATENCY;
m_rtcEngine->joinChannel(token, channelName, 0, options);
}
void CAgoraQuickStartDlg::setupRemoteVideo() {
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() {
VideoCanvas canvas;
canvas.renderMode = RENDER_MODE_TYPE::RENDER_MODE_HIDDEN;
canvas.uid = 0;
canvas.view = m_staLocal.GetSafeHwnd();
m_rtcEngine->setupLocalVideo(canvas);
m_rtcEngine->startPreview();
}
void CAgoraQuickStartDlg::OnBnClickedBtnJoin() {
CString strChannelName;
m_edtChannelName.GetWindowText(strChannelName);
if (strChannelName.IsEmpty()) {
AfxMessageBox(_T("Fill channel name first"));
return;
}
joinChannel(token, CW2A(strChannelName));
setupLocalVideo();
}
void CAgoraQuickStartDlg::OnBnClickedBtnLeave() {
m_rtcEngine->leaveChannel();
VideoCanvas canvas;
canvas.uid = 0;
m_rtcEngine->setupLocalVideo(canvas);
m_rtcEngine->stopPreview();
m_remoteRender = false;
}
LRESULT CAgoraQuickStartDlg::OnEIDJoinChannelSuccess(WPARAM wParam, LPARAM lParam) {
uid_t localUid = wParam;
return 0;
}
LRESULT CAgoraQuickStartDlg::OnEIDUserJoined(WPARAM wParam, LPARAM lParam) {
uid_t remoteUid = wParam;
if (m_remoteRender) {
return 0;
}
setupRemoteVideo();
return 0;
}
LRESULT CAgoraQuickStartDlg::OnEIDUserOffline(WPARAM wParam, LPARAM lParam) {
uid_t remoteUid = wParam;
if (!m_remoteRender) {
return 0;
}
VideoCanvas canvas;
canvas.uid = remoteUid;
m_rtcEngine->setupRemoteVideo(canvas);
m_remoteRender = false;
return 0;
}
```
### 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**
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:

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 to this product.
* If a firewall is deployed in your network environment, refer to [Connect with Cloud Proxy](build/optimize-quality-and-connection/cloud-proxy.mdx) to use Agora services normally.
### Next steps [#next-steps-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](build/authenticate-users/use-tokens.mdx).
### 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-3]
* [How can I fix black screen issues?](/en/api-reference/faq/quality/video_blank)
* [Why can't I turn on the camera?](/en/api-reference/faq/quality/video_camera)
* [How can I listen for audience joining or leaving a channel?](/en/api-reference/faq/integration/audience_event)
* [How can I solve channel-related issues?](/en/api-reference/faq/integration/channel)
* [How can I set the log file?](/en/api-reference/faq/integration/set_log_file)
### See also [#see-also-4]
* [Error codes](/en/realtime-media/video/reference/error-codes)
* [Connection status management](build/optimize-quality-and-connection/connection-status-management.mdx)
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="electron">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="electron" />
This page provides a step-by-step guide on how to create a basic Interactive Live Streaming app using the Agora Video SDK.
## Understand the tech [#understand-the-tech-5]
To start a Interactive Live Streaming session, implement the following steps in your app:
* **Initialize the Agora Engine**: Before calling other APIs, create and initialize an Agora Engine instance.
* **Join a channel**: Call methods to create and join a channel.
* **Join as a host**: A live streaming event has one or more hosts. A host publishes audio and video to the channel. Hosts can also subscribe to streams from other hosts.
* **Join as audience**: Audience members can only subscribe to streams published by hosts.
* **Send and receive audio and video**: Hosts publish streams to the channel. Audience members subscribe to audio and video streams published by hosts.

## Prerequisites [#prerequisites-5]
* [Node.js](https://nodejs.org/en/download/) 14 or higher.
* A camera and a microphone
* A valid Agora account and project. Please refer to [Agora account management](manage-agora-account.md) for details.
## Set up your project [#set-up-your-project-5]
This section shows you how to set up your Electron project and install the Agora Video SDK.
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]
**npm integration**
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.
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.
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
```
* 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.
**Manual Integration**
Compile the latest SDK from the [Electron SDK repository](https://github.com/AgoraIO-Extensions/Electron-SDK/tree/main/) and integrate it into your application.
## Implement Interactive Live Streaming [#implement-interactive-live-streaming-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:

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,
AudienceLatencyLevelType
} = 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](manage-agora-account.md). Call `registerEventHandler` to register a custom [event handler](#subscribe-to--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](build/authenticate-users/deploy-token-server.mdx) in your security infrastructure. For the purpose of this guide [Generate a temporary token](manage-agora-account.md).
* **User ID**: A 32-bit signed integer that identifies a user in the channel. You can specify a unique user ID for each user yourself. If you set the user ID to `0` when joining a channel, the SDK generates a random number for the user ID and returns the value in the `onJoinChannelSuccess` callback.
* **Channel media options**: Configure `ChannelMediaOptions` to define publishing and subscription settings, optimize performance for your specific use-case, and set optional parameters.
For Interactive Live Streaming, set the `channelProfile` to `ChannelProfileLiveBroadcasting`, the `clientRoleType` to `ClientRoleBroadcaster` (host) or `ClientRoleAudience`, and the `audienceLatencyLevelType` to `AudienceLatencyLevelUltraLowLatency`.
```js
// Join the channel using a temporary token
rtcEngine.joinChannel(token, channel, uid, {
// Set the channel profile to live broadcasting
channelProfile: ChannelProfileType.ChannelProfileLiveBroadcasting,
// 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,
// For live streaming use-case (This setting takes effect only when the user role is set to ClientRoleAudience )
audienceLatencyLevelType: AudienceLatencyLevelType.AudienceLatencyLevelUltraLowLatency,
});
```
### 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);
```
To ensure that you receive all Video SDK events, set the Agora Engine event handler before joining a channel.
### 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 app receives the `onUserJoined` callback.
### Start and close the app [#start-and-close-the-app-3]
When a user launches your app, start Interactive Live Streaming. When a user closes the app, stop Interactive Live Streaming and release resources.
1. To start Interactive Live Streaming, [initialize the engine](#initialize-the-engine), [display the local video](#display-the-local-video) and [join a channel](#join-a-channel).
2. When Interactive Live Streaming 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();
```
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.
### 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`**
```js
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`**
```js
const {
createAgoraRtcEngine,
ChannelProfileType,
ClientRoleType,
VideoSourceType,
VideoViewSetupMode,
AudienceLatencyLevelType
} = 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 live broadcasting
channelProfile: ChannelProfileType.ChannelProfileLiveBroadcasting,
// 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,
// For live streaming use-case (This setting takes effect only when the user role is set to ClientRoleAudience )
audienceLatencyLevelType: AudienceLatencyLevelType.AudienceLatencyLevelUltraLowLatency,
});
};
```
* 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](#get-device-permissions).
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**
```html
Electron Quickstart
Electron Quickstart
```
## 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 to this product.
* If a firewall is deployed in your network environment, refer to [Connect with Cloud Proxy](build/optimize-quality-and-connection/cloud-proxy.mdx) to use Agora services normally.
### Next steps [#next-steps-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](build/authenticate-users/use-tokens.mdx).
### 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-4]
* [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)
* [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]
* [Error codes](/en/realtime-media/video/reference/error-codes)
* [Connection status management](build/optimize-quality-and-connection/connection-status-management.mdx)
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="flutter">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="flutter" />
This page provides a step-by-step guide on how to create a basic Interactive Live Streaming app using the Agora Video SDK.
## Understand the tech [#understand-the-tech-6]
To start a Interactive Live Streaming session, implement the following steps in your app:
* **Initialize the Agora Engine**: Before calling other APIs, create and initialize an Agora Engine instance.
* **Join a channel**: Call methods to create and join a channel.
* **Join as a host**: A live streaming event has one or more hosts. A host publishes audio and video to the channel. Hosts can also subscribe to streams from other hosts.
* **Join as audience**: Audience members can only subscribe to streams published by hosts.
* **Send and receive audio and video**: Hosts publish streams to the channel. Audience members subscribe to audio and video streams published by hosts.

## Prerequisites [#prerequisites-6]
* [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 any other 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](reference/supported-platforms.md).
Run the `flutter doctor` command to confirm that your development environment is set up correctly for Flutter development.
* A camera and a microphone
* A valid Agora account and project. Please refer to [Agora account management](manage-agora-account.md) for details.
## Set up your project [#set-up-your-project-6]
This section shows you how to set up your Flutter project and install the Agora Video SDK.
**Create a new project**
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
```
**Add to an existing project**
To add Interactive Live Streaming 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`.
### 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
```
1. Install the dependencies.
Execute the following command in the project path:
```bash
flutter pub get
```
## Implement Interactive Live Streaming [#implement-interactive-live-streaming-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:

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](manage-agora-account.md), and other configuration parameters. In your dart file, add the following code:
```dart
// Set up the Agora RTC engine instance
Future _initializeAgoraVideoSDK() async {
_engine = createAgoraRtcEngine();
await _engine.initialize(const RtcEngineContext(
appId: appId,
channelProfile: ChannelProfileType.channelProfileLiveBroadcasting,
));
}
```
### 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](build/authenticate-users/deploy-token-server.mdx) in your security infrastructure. For the purpose of this guide [Generate a temporary token](manage-agora-account.md).
* **User ID**: A 32-bit signed integer that identifies a user in the channel. You can specify a unique user ID for each user yourself. If you set the user ID to `0` when joining a channel, the SDK generates a random number for the user ID and returns the value in the `onJoinChannelSuccess` callback.
* **Channel media options**: Configure `ChannelMediaOptions` to define publishing and subscription settings, optimize performance for your specific use-case, and set optional parameters.
For Interactive Live Streaming, set the `clientRoleType` to `clientRoleBroadcaster` (host) or `clientRoleAudience`, and the `audienceLatencyLevel` to `audienceLatencyLevelUltraLowLatency`.
```dart
// Join a channel
Future _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,
// Set the audience latency level
audienceLatencyLevel: AudienceLatencyLevelType.audienceLatencyLevelUltraLowLatency
),
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.
To ensure that you receive all Video SDK events, register the event handler before joining a channel.
### 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 _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 Interactive Live Streaming.
```dart
Future _requestPermissions() async {
await [Permission.microphone, Permission.camera].request();
}
```
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 |
### Start and close the app [#start-and-close-the-app-4]
To start Interactive Live Streaming, 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 Interactive Live Streaming, leave the channel and release the engine instance.
```dart
// Leaves the channel and releases resources
Future _cleanupAgoraEngine() async {
await _engine.leaveChannel();
await _engine.release();
}
```
After you call `release`, you no longer have access to the methods and callbacks of the SDK. To use Interactive Live Streaming features again, create a new engine instance.
### 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.
```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();
_startLiveStreaming();
}
// Initializes Agora SDK
Future _startLiveStreaming() async {
await _requestPermissions();
await _initializeAgoraVideoSDK();
await _setupLocalVideo();
_setupEventHandlers();
await _joinChannel();
}
// Requests microphone and camera permissions
Future _requestPermissions() async {
await [Permission.microphone, Permission.camera].request();
}
// Set up the Agora RTC engine instance
Future _initializeAgoraVideoSDK() async {
_engine = createAgoraRtcEngine();
await _engine.initialize(const RtcEngineContext(
appId: appId,
channelProfile: ChannelProfileType.channelProfileLiveBroadcasting,
));
}
// Enables and starts local video preview
Future _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 _joinChannel() async {
await _engine.joinChannel(
token: token,
channelId: channel,
options: const ChannelMediaOptions(
autoSubscribeVideo: true,
autoSubscribeAudio: true,
publishCameraTrack: true,
publishMicrophoneTrack: true,
clientRoleType: ClientRoleType.clientRoleBroadcaster,
audienceLatencyLevel: AudienceLatencyLevelType.audienceLatencyLevelUltraLowLatency
),
uid: 0,
);
}
@override
void dispose() {
_cleanupAgoraEngine();
super.dispose();
}
// Leaves the channel and releases resources
Future _cleanupAgoraEngine() async {
await _engine.leaveChannel();
await _engine.release();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Agora Interactive Live Streaming')),
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,
);
}
}
}
```
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.
### 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**
```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 app. 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:

## Reference [#reference-6]
This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.
* If a firewall is deployed in your network environment, refer to [Connect with Cloud Proxy](build/optimize-quality-and-connection/cloud-proxy.mdx) to use Agora services normally.
### Next steps [#next-steps-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](build/authenticate-users/use-tokens.mdx).
### 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-5]
* [How can I fix black screen issues?](/en/api-reference/faq/quality/video_blank)
* [Why can't I turn on the camera?](/en/api-reference/faq/quality/video_camera)
* [How can I listen for audience joining or leaving a channel?](/en/api-reference/faq/integration/audience_event)
* [How can I solve channel-related issues?](/en/api-reference/faq/integration/channel)
* [How can I set the log file?](/en/api-reference/faq/integration/set_log_file)
### See also [#see-also-6]
* [Error codes](/en/realtime-media/video/reference/error-codes)
* [Connection status management](build/optimize-quality-and-connection/connection-status-management.mdx)
<_PlatformProcessedMarker close="true" />
<_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 Interactive Live Streaming app using the Agora Video SDK.
## Understand the tech [#understand-the-tech-7]
To start a Interactive Live Streaming session, implement the following steps in your app:
* **Initialize the Agora Engine**: Before calling other APIs, create and initialize an Agora Engine instance.
* **Join a channel**: Call methods to create and join a channel.
* **Join as a host**: A live streaming event has one or more hosts. A host publishes audio and video to the channel. Hosts can also subscribe to streams from other hosts.
* **Join as audience**: Audience members can only subscribe to streams published by hosts.
* **Send and receive audio and video**: Hosts publish streams to the channel. Audience members subscribe to audio and video streams published by hosts.

## Prerequisites [#prerequisites-7]
* React Native 0.60 or later. For more information, see [Get Started with React Native](https://reactnative.dev/docs/environment-setup).
* Node 16 or later
For your target platform, prepare the following:
Android
iOS
* A machine running macOS, Windows, or Linux
* [Java Development Kit (JDK) 11](https://openjdk.org/projects/jdk/11/) or later
* Latest version of Android Studio
* A physical or virtual mobile device running Android 5.0 or later
* A machine running macOS
* Xcode 10 or later
* CocoaPods
* A physical or virtual mobile device running iOS 9.0 or later.
If you use React Native 0.63 or later, ensure your iOS version is 10.0 or later.
* A camera and a microphone
* A valid Agora account and project. Please refer to [Agora account management](manage-agora-account.md) for details.
## Set up your project [#set-up-your-project-7]
This section shows you how to set up your React Native project and install the Agora Video SDK.
### Create a project [#create-a-project]
To create a new application using [React Native Community CLI](https://github.com/react-native-community/cli), see [Get Started Without a Framework](https://reactnative.dev/docs/getting-started-without-a-framework).
### Install the SDK [#install-the-sdk-7]
Download and add the Agora Video SDK to your React Native project. Choose either of the following methods:
**npm**
In your project folder, execute the following command:
```bash
npm i --save react-native-agora
```
**yarn**
In your project folder, execute the following commands:
```bash
# Install yarn
npm install -g yarn
# Use yarn to download Agora React Native SDK
yarn add react-native-agora
```
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
## Implement Interactive Live Streaming [#implement-interactive-live-streaming-7]
This section guides you through the implementation of basic real-time audio and video interaction in your app.
The following figure illustrates the essential steps:

This guide includes [complete sample code](#complete-sample-code) that demonstrates implementing basic real-time interaction. To understand the core API calls in the sample code, review the following implementation steps and use the code in your `App.tsx` file.
### Import dependencies [#import-dependencies-1]
Add the required Agora dependencies to your app.
```tsx
import {
createAgoraRtcEngine,
ChannelProfileType,
ClientRoleType,
AudienceLatencyLevelType,
IRtcEngine,
RtcSurfaceView,
RtcConnection,
IRtcEngineEventHandler,
} from 'react-native-agora';
```
### Initialize the engine [#initialize-the-engine-6]
For real-time communication, call `createAgoraRtcEngine` to create an `RtcEngine` instance, and then `initialize` the engine using `RtcEngineContext` to specify the [App ID](manage-agora-account.md).
```tsx
const appId = '<-- Insert app ID -->';
const setupVideoSDKEngine = async () => {
if (Platform.OS === 'android') { await getPermission(); }
agoraEngineRef.current = createAgoraRtcEngine();
const agoraEngine = agoraEngineRef.current;
await agoraEngine.initialize({ appId: appId });
};
```
### Join a channel [#join-a-channel-7]
To join a channel, call `joinChannel` with the following parameters:
* **Channel name**: The name of the channel to join. Clients that pass the same channel name join the same channel. If a channel with the specified name does not exist, it is created when the first user joins.
* **Authentication token**: A dynamic key that authenticates a user when the client joins a channel. In a production environment, you obtain a token from a [token server](build/authenticate-users/deploy-token-server.mdx) in your security infrastructure. For the purpose of this guide [Generate a temporary token](manage-agora-account.md).
* **User ID**: A 32-bit signed integer that identifies a user in the channel. You can specify a unique user ID for each user yourself. If you set the user ID to `0` when joining a channel, the SDK generates a random number for the user ID and returns the value in the `onJoinChannelSuccess` callback.
* **Channel media options**: Configure `ChannelMediaOptions` to define publishing and subscription settings, optimize performance for your specific use-case, and set optional parameters.
For Interactive Live Streaming, set the `channelProfile` to `ChannelProfileLiveBroadcasting`, the `clientRoleType` to `ClientRoleBroadcaster` (host) or `ClientRoleAudience`, and the `audienceLatencyLevel` to `audienceLatencyLevelUltraLowLatency`.
```tsx
const token = '<-- Insert token -->';
const channelName = '<-- Insert channel name -->';
const localUid = 0; // Local user UID, no need to modify
// Define the join method called after clicking the join channel button
const join = async () => {
if (isJoined) {
return;
}
if (isHost) {
// Join the channel as a broadcaster
agoraEngineRef.current?.joinChannel(token, channelName, localUid, {
// Set channel profile to live broadcast
channelProfile: ChannelProfileType.ChannelProfileLiveBroadcasting,
// Set user role to broadcaster
clientRoleType: ClientRoleType.ClientRoleBroadcaster,
// Publish audio collected by the microphone
publishMicrophoneTrack: true,
// Publish video collected by the camera
publishCameraTrack: true,
// Automatically subscribe to all audio streams
autoSubscribeAudio: true,
// Automatically subscribe to all video streams
autoSubscribeVideo: true,
});
} else {
// Join the channel as an audience
agoraEngineRef.current?.joinChannel(token, channelName, localUid, {
// Set channel profile to live broadcast
channelProfile: ChannelProfileType.ChannelProfileLiveBroadcasting,
// Set user role to audience
clientRoleType: ClientRoleType.ClientRoleAudience,
// Do not publish audio collected by the microphone
publishMicrophoneTrack: false,
// Do not publish video collected by the camera
publishCameraTrack: false,
// Automatically subscribe to all audio streams
autoSubscribeAudio: true,
// Automatically subscribe to all video streams
autoSubscribeVideo: true,',
// Change the delay level of the audience to achieve ultra-fast live broadcast
audienceLatencyLevel: AudienceLatencyLevelType.audienceLatencyLevelUltraLowLatency,
});
}
};
```
Since the Agora Engine runs locally, reloading the app using the Metro bundler may cause the engine reference to be lost, preventing channel joining. To resolve this issue, restart the app.
### 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);
};
```
To ensure that you receive all Video SDK events, register the event handler before joining a channel.
### 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
```
### 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
```
### Handle permissions [#handle-permissions-5]
To access the camera and microphone, ensure that the user grants the necessary permissions when the app starts.
Android
iOS
On Android devices, pop up a prompt box to obtain permission to use the camera and microphone.
```tsx
// Import components related to Android device permissions
import {PermissionsAndroid, Platform} from 'react-native';
const getPermission = async () => {
if (Platform.OS === 'android') {
await PermissionsAndroid.requestMultiple([
PermissionsAndroid.PERMISSIONS.RECORD_AUDIO,
PermissionsAndroid.PERMISSIONS.CAMERA,
]);
}
};
```
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. |
### Leave the channel [#leave-the-channel-2]
To exit a Interactive Live Streaming channel, call `leaveChannel`.
```tsx
const leave = () => {
// Leave the channel
agoraEngineRef.current?.leaveChannel();
setIsJoined(false);
};
```
### Clean up resources [#clean-up-resources]
Before closing your app, unregister the event handler and call `release` to free up resources.
```tsx
const cleanupAgoraEngine = () => {
return () => {
agoraEngineRef.current?.unregisterEventHandler(eventHandler.current!);
agoraEngineRef.current?.release();
};
};
```
After calling `release,` methods and callbacks of the SDK are no longer available. To use the SDK functions again, create a new engine instance.
### 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.
```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 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(); // Implement callback functions
useEffect(() => {
const init = async () => {
await setupVideoSDKEngine();
setupEventHandler();
};
init();
return () => {
cleanupAgoraEngine(); // Ensure this is synchronous
};
}, []); // Empty dependency array ensures it runs only once
const setupEventHandler = () => {
eventHandler.current = {
onJoinChannelSuccess: () => {
setMessage('Successfully joined channel: ' + channelName);
setupLocalVideo();
setIsJoined(true);
},
onUserJoined: (_connection: RtcConnection, uid: number) => {
setMessage('Remote user ' + uid + ' joined');
setRemoteUid(uid);
},
onUserOffline: (_connection: RtcConnection, uid: number) => {
setMessage('Remote user ' + uid + ' left the channel');
setRemoteUid(uid);
},
};
agoraEngineRef.current?.registerEventHandler(eventHandler.current);
};
const setupVideoSDKEngine = async () => {
try {
if (Platform.OS === 'android') { await getPermission(); }
agoraEngineRef.current = createAgoraRtcEngine();
const agoraEngine = agoraEngineRef.current;
await agoraEngine.initialize({ appId: appId });
} catch (e) {
console.error(e);
}
};
const setupLocalVideo = () => {
agoraEngineRef.current?.enableVideo();
agoraEngineRef.current?.startPreview();
};
// Define the join method called after clicking the join channel button
const join = async () => {
if (isJoined) {
return;
}
try {
if (isHost) {
// Join the channel as a broadcaster
agoraEngineRef.current?.joinChannel(token, channelName, localUid, {
// Set channel profile to live broadcast
channelProfile: ChannelProfileType.ChannelProfileLiveBroadcasting,
// Set user role to broadcaster
clientRoleType: ClientRoleType.ClientRoleBroadcaster,
// Publish audio collected by the microphone
publishMicrophoneTrack: true,
// Publish video collected by the camera
publishCameraTrack: true,
// Automatically subscribe to all audio streams
autoSubscribeAudio: true,
// Automatically subscribe to all video streams
autoSubscribeVideo: true,
});
} else {
// Join the channel as an audience
agoraEngineRef.current?.joinChannel(token, channelName, localUid, {
// Set channel profile to live broadcast
channelProfile: ChannelProfileType.ChannelProfileLiveBroadcasting,
// Set user role to audience
clientRoleType: ClientRoleType.ClientRoleAudience,
// Do not publish audio collected by the microphone
publishMicrophoneTrack: false,
// Do not publish video collected by the camera
publishCameraTrack: false,
// Automatically subscribe to all audio streams
autoSubscribeAudio: true,
// Automatically subscribe to all video streams
autoSubscribeVideo: true,',
// Change the delay level of the audience to achieve ultra-fast live broadcast
audienceLatencyLevel: AudienceLatencyLevelType.audienceLatencyLevelUltraLowLatency,
});
}
} catch (e) {
console.log(e);
}
};
// Define the leave method called after clicking the leave channel button
const leave = () => {
try {
// Call leaveChannel method to leave the channel
agoraEngineRef.current?.leaveChannel();
setRemoteUid(0);
setIsJoined(false);
showMessage('Left the channel');
} catch (e) {
console.log(e);
}
};
const cleanupAgoraEngine = () => {
return () => {
agoraEngineRef.current?.unregisterEventHandler(eventHandler.current!);
agoraEngineRef.current?.release();
};
};
// Render user interface
return (
Agora Video SDK Quickstart
Join Channel
Leave Channel
Audience {
setIsHost(switchValue);
if (isJoined) {
leave();
}
}}
value={isHost}
/>
Host
{isJoined && isHost ? (
Local user uid: {localUid}
) : (
Join a channel
)}
{isJoined && remoteUid !== 0 ? (
Remote user uid: {remoteUid}
) : (
{isJoined && !isHost ? 'Waiting for remote user to join' : ''}
)}
{message}
);
// 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;
```
### Create a user interface [#create-a-user-interface-7]
Design a user interface for your project based on your application use-case. A basic interface consists of two view frames to display local and remote videos. Add **Join channel** and **Leave channel** buttons to enable the user to join and leave a channel. To create such an interface, refer to the following code:
**Sample code to create the user interface**
```tsx
import React, { useState } from 'react';
// Import user interface elements
import {
SafeAreaView,
ScrollView,
StyleSheet,
Text,
View,
Switch,
} from 'react-native';
const localUid = 0; // Local user Uid, no need to modify
const App = () => {
const [isJoined, setIsJoined] = useState(false); // Whether the local user has joined the channel
const [isHost, setIsHost] = useState(true); // User role
const [remoteUid, setRemoteUid] = useState(0); // Uid of the remote user
const [message, setMessage] = useState(''); // User prompt message
// Render user interface
return (
Agora Video SDK Quickstart
Join Channel
Leave Channel
Audience {
setIsHost(switchValue);
if (isJoined) {
leave();
}
}}
value={isHost}
/>
Host
{isJoined && isHost ? (
Local user uid: {localUid}
) : (
Join a channel
)}
{isJoined && remoteUid !== 0 ? (
Remote user uid: {remoteUid}
) : (
{isJoined && !isHost ? 'Waiting for remote user to join' : ''}
)}
{message}
);
};
// 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.
Some simulators may not support all the functions of this project. Best practice is to run the project on a physical device.
Android
iOS
To run the app on an Android device:
3. Turn on the developer options of the Android device and connect the Android device to the computer through a USB cable.
4. In the project root directory, execute `npx react-native run-android`.
To run on the app on an iOS device:
5. Open the `ProjectName/ios/ProjectName.xcworkspace` folder using Xcode.
6. Connect the iOS device to your computer through a USB cable.
7. In Xcode, click the **Build and Run** button.
For detailed steps on running the app on a real Android or iOS device, please refer to [Running On Device](https://reactnative.dev/docs/running-on-device).
8. Launch the app and click the **Join channel** button to join a channel.
9. Invite a friend to install and run the app on a second device. Alternatively, use the [Web demo](https://webdemo.agora.io/basicVideoCall/index.html) to join the same channel. Once your friend joins the channel, you can see and hear each other.
\:::
## Reference [#reference-7]
This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.
* If a firewall is deployed in your network environment, refer to [Connect with Cloud Proxy](build/optimize-quality-and-connection/cloud-proxy.mdx) to use Agora services normally.
### Next steps [#next-steps-7]
After implementing the quickstart sample, read the following documents to learn more:
* To ensure communication security in a test or production environment, best practice is to obtain and use a token from an authentication server. For details, see [Secure authentication with tokens](build/authenticate-users/use-tokens.mdx).
### Sample project [#sample-project-7]
Agora provides open source sample projects on [GitHub](https://github.com/AgoraIO-Extensions/react-native-agora/blob/main/example/src/examples) for your reference. Download or view the [JoinChannelVideo](https://github.com/AgoraIO-Extensions/react-native-agora/blob/main/example/src/examples/basic/JoinChannelVideo) project for a more detailed example.
### API reference [#api-reference-7]
* [`createAgoraRtcEngine`](https://api-ref.agora.io/en/video-sdk/react-native/4.x/API/class_irtcengine.html#api_createagorartcengine)
* [`initialize`](https://api-ref.agora.io/en/video-sdk/react-native/4.x/API/class_irtcengine.html#api_irtcengine_initialize)
* [`registerEventHandler`](https://api-ref.agora.io/en/video-sdk/react-native/4.x/API/class_irtcengine.html#api_irtcengine_addhandler)
* [`setChannelProfile`](https://api-ref.agora.io/en/video-sdk/react-native/4.x/API/class_irtcengine.html#api_irtcengine_setchannelprofile)
* [`joinChannel`](https://api-ref.agora.io/en/video-sdk/react-native/4.x/API/class_irtcengine.html#api_irtcengine_joinchannel2)
* [`leaveChannel`](https://api-ref.agora.io/en/video-sdk/react-native/4.x/API/class_irtcengine.html#api_irtcengine_leavechannel2)
### Frequently asked questions [#frequently-asked-questions-6]
* [How can I fix black screen issues?](/en/api-reference/faq/quality/video_blank)
* [Why can't I turn on the camera?](/en/api-reference/faq/quality/video_camera)
* [How can I listen for audience joining or leaving a channel?](/en/api-reference/faq/integration/audience_event)
* [How can I solve channel-related issues?](/en/api-reference/faq/integration/channel)
* [How can I set the log file?](/en/api-reference/faq/integration/set_log_file)
### See also [#see-also-7]
* [Error codes](/en/realtime-media/video/reference/error-codes)
* [Connection status management](build/optimize-quality-and-connection/connection-status-management.mdx)
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="javascript">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="javascript" />
This page provides a step-by-step guide on how to create a basic Interactive Live Streaming app using the Agora Video SDK.
## Understand the tech [#understand-the-tech-8]
To start a Interactive Live Streaming session, implement the following steps in your app:
* **Initialize the Agora Engine**: Before calling other APIs, create and initialize an Agora Engine instance.
* **Join a channel**: Call methods to create and join a channel.
* **Join as a host**: A live streaming event has one or more hosts. A host publishes audio and video to the channel. Hosts can also subscribe to streams from other hosts.
* **Join as audience**: Audience members can only subscribe to streams published by hosts.
* **Send and receive audio and video**: Hosts publish streams to the channel. Audience members subscribe to audio and video streams published by hosts.

React SDK and Web SDK relationship
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.
## Prerequisites [#prerequisites-8]
* A [supported browser](/en/realtime-media/interactive-live-streaming/reference/supported-platforms).
* A JavaScript package manager such as [npm](https://www.npmjs.com/package/npm).
* A camera and a microphone
* A valid Agora account and project. Please refer to [Agora account management](manage-agora-account.md) for details.
## Set up your project [#set-up-your-project-8]
This section shows you how to set up your ReactJS project and install the Agora Video SDK.
### 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:
**NPM**
Navigate to the project folder and run the following command to install the Video SDK:
```bash
npm i agora-rtc-react
```
**CDN**
To integrate React JS dependencies and the Agora SDK using CDN, add the following code to your HTML file:
```html
```
## Implement Interactive Live Streaming [#implement-interactive-live-streaming-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:
```tsx
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";
import AgoraRTC, { AgoraRTCProvider } from "agora-rtc-react";
```
### Initialize the client [#initialize-the-client]
Use the `createClient` method provided by the SDK to create a client object for the `` component. Wrap the `` component with `` to enable state management and access operation-related hooks.
For Interactive Live Streaming, set the `mode` property of `ClientConfig` to `"live"` and the `role` to `"host"` or `"audience"`.
```tsx
export InteractiveLiveStreaming = () => {
const client = AgoraRTC.createClient({ mode: "live", codec: "vp8" });
return(
);
}
```
### Join a channel [#join-a-channel-8]
To join a channel, use the `useJoin` hook. You can specify the following `joinOptions`:
* **App ID**: [`appid`](manage-agora-account.md) 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](build/authenticate-users/deploy-token-server.mdx) in your security infrastructure. For the purpose of this guide [Generate a temporary token](manage-agora-account.md).
* **User ID**: `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`:
```tsx
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`:
```tsx
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`:
```tsx
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:
```tsx
```
### 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:
```tsx
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:
```tsx
{remoteUsers.map((user) => (
{user.uid}
))}
```
### 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.
```tsx
import {
LocalUser,
RemoteUser,
useIsConnected,
useJoin,
useLocalMicrophoneTrack,
useLocalCameraTrack,
usePublish,
useRemoteUsers,
} from "agora-rtc-react";
import { useState } from "react";
import AgoraRTC, { AgoraRTCProvider } from "agora-rtc-react";
const client = AgoraRTC.createClient({ mode: "live", codec: "vp8" });
return(
);
}
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 (
<>
)}
>
);
};
export default InteractiveLiveStreaming;
```
## Test the sample code [#test-the-sample-code-8]
Use [CodeSandbox](https://codesandbox.io/), to swiftly run the complete demo code and experience the React Video SDK functionality in just a few simple steps.
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.
If `CodeSandbox` access is slow, try adjusting your network configuration.
## Reference [#reference-8]
This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.
* If a firewall is deployed in your network environment, refer to [Connect with Cloud Proxy](build/optimize-quality-and-connection/cloud-proxy.mdx) to use Agora services normally.
### Next steps [#next-steps-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](build/authenticate-users/use-tokens.mdx).
### 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-8]
* [Error codes](/en/realtime-media/video/reference/error-codes)
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="unity">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="unity" />
This page provides a step-by-step guide on how to create a basic Interactive Live Streaming app using the Agora Video SDK.
## Understand the tech [#understand-the-tech-9]
To start a Interactive Live Streaming session, implement the following steps in your app:
* **Initialize the Agora Engine**: Before calling other APIs, create and initialize an Agora Engine instance.
* **Join a channel**: Call methods to create and join a channel.
* **Join as a host**: A live streaming event has one or more hosts. A host publishes audio and video to the channel. Hosts can also subscribe to streams from other hosts.
* **Join as audience**: Audience members can only subscribe to streams published by hosts.
* **Send and receive audio and video**: Hosts publish streams to the channel. Audience members subscribe to audio and video streams published by hosts.

## Prerequisites [#prerequisites-9]
* [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 |
* A camera and a microphone
* A valid Agora account and project. Please refer to [Agora account management](manage-agora-account.md) for details.
## Set up your project [#set-up-your-project-9]
This section shows you how to set up your Unity project and install the Agora Video SDK.
**Create a new project**
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**.
**Add to an existing project**
To open your existing project:
4. In the **Projects** window, click the **Open** button in the top-right corner.
5. Browse your file manager and select the folder of the project you want to open.
6. Confirm your selection to add the project to the **Projects** window and open it in the Unity Editor.
### Install the SDK [#install-the-sdk-9]
1. Go to the [Download SDKs](/en/api-reference/sdks?product=video\&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 Interactive Live Streaming [#implement-interactive-live-streaming-9]
This section guides you through the implementation of basic real-time audio and video interaction in your game.
The following figure illustrates the essential steps:

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 Interactive Live Streaming and bind the script to the canvas.
**Steps to set up a script**
1. Create a new script and import the UI library.
2. 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.
3. Rename the file to `JoinChannel.cs` and open it.
4. Import the Unity namespaces to access UI components by adding the following code at the top of the file:
```csharp
using UnityEngine;
using UnityEngine.UI;
```
5. 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.
```csharp
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:
```csharp
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_LIVE_BROADCASTING;
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](build/authenticate-users/deploy-token-server.mdx) in your security infrastructure. For the purpose of this guide [Generate a temporary token](manage-agora-account.md).
* **User ID**: A 32-bit signed integer that identifies a user in the channel. You can specify a unique user ID for each user yourself. If you set the user ID to `0` when joining a channel, the SDK generates a random number for the user ID and returns the value in the `OnJoinChannelSuccess` callback.
* **Channel media options**: Configure `ChannelMediaOptions` to define publishing and subscription settings, optimize performance for your specific use-case, and set optional parameters.
For Interactive Live Streaming, set the `channelProfile` to `CHANNEL_PROFILE_LIVE_BROADCASTING`, the `clientRoleType` to `CLIENT_ROLE_BROADCASTER` (host) or `CLIENT_ROLE_AUDIENCE`, and the `audienceLatencyLevel` to `AUDIENCE_LATENCY_LEVEL_ULTRA_LOW_LATENCY`.
```csharp
// 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();
// Start video rendering
LocalView.SetEnable(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 broadcast
options.channelProfile.SetValue(CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_LIVE_BROADCASTING);
//Set the user role as host
options.clientRoleType.SetValue(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
// Set the audience latency level
options.audienceLatencyLevel.SetValue(AUDIENCE_LATENCY_LEVEL_TYPE.AUDIENCE_LATENCY_LEVEL_ULTRA_LOW_LATENCY);
// Join a 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.
```csharp
// 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);
}
```
To ensure that you receive all Video SDK events, register the event handler before joining a channel.
### 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.
```csharp
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.
```csharp
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 camera and microphone, add device permissions to your project according to your target platform.
**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:
```csharp
#if (UNITY_2018_3_OR_NEWER && UNITY_ANDROID)
using UnityEngine.Android;
#endif
```
2. Create a list of permissions to be obtained.
```csharp
#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.
```csharp
private void CheckPermissions() {
#if (UNITY_2018_3_OR_NEWER && UNITY_ANDROID)
foreach (string permission in permissionList) {
if (!Permission.HasUserAuthorizedPermission(permission)) {
Permission.RequestUserPermission(permission);
}
}
#endif
}
```
**iOS and macOS**
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.
### Start and stop your game [#start-and-stop-your-game]
1. When the game starts, ensure that device permissions have been granted.
```csharp
void Update() {
CheckPermissions();
}
```
2. To start Interactive Live Streaming, initialize the engine and set up the event handler.
```csharp
void Start()
{
SetupVideoSDKEngine();
InitEventHandler();
PreviewSelf();
}
```
3. To clean up all session-related resources when a user exits the game, call the `Dispose` method of the `IRtcEngine`.
```csharp
void OnApplicationQuit() {
if (RtcEngine != null) {
Leave();
// Destroy IRtcEngine
RtcEngine.Dispose();
RtcEngine = null;
}
}
```
After calling `Dispose`, you can no longer use any methods or callbacks of the SDK. To use Interactive Live Streaming features again, create a new engine instance.
### 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 Interactive Live Streaming in your game**
```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();
go.transform.Rotate(0.0f, 0.0f, -180.0f);
go = GameObject.Find("RemoteView");
RemoteView = go.AddComponent();
go.transform.Rotate(0.0f, 0.0f, -180.0f);
go = GameObject.Find("Leave");
go.GetComponent().onClick.AddListener(Leave);
go = GameObject.Find("Join");
go.GetComponent().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");
}
}
}
```
### 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**
1. Create buttons to join and leave channel
2. In your Unity project, right-click the **Sample Scene** and select **Game Object > UI > Button**. You see a button on the scene canvas.
3. In the **Inspector** panel, rename the button to `Join` and adjust the position coordinates as needed. For example:
* **Pos X**:`-329`
* **Pos Y**: `-172`
4. Select the **Text** control of the **Join** button , and change the text to `Join` in the **Inspector** panel.
5. Repeat the steps to create a **Leave** button, using the following positions:
* **Pos X**:`329`
* **Pos Y**: `-172`
6. Create local and remote view windows
7. Right-click the Canvas and select **UI > Raw Image**.
8. 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`
9. 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:

## 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 game 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 to this product.
* If a firewall is deployed in your network environment, refer to [Connect with Cloud Proxy](build/optimize-quality-and-connection/cloud-proxy.mdx) to use Agora services normally.
### Next steps [#next-steps-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](build/authenticate-users/use-tokens.mdx).
### 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-7]
* [How can I fix black screen issues?](/en/api-reference/faq/quality/video_blank)
* [Why can't I turn on the camera?](/en/api-reference/faq/quality/video_camera)
* [How can I listen for audience joining or leaving a channel?](/en/api-reference/faq/integration/audience_event)
* [How can I solve channel-related issues?](/en/api-reference/faq/integration/channel)
### See also [#see-also-9]
* [Error codes](/en/realtime-media/video/reference/error-codes)
* [Connection status management](build/optimize-quality-and-connection/connection-status-management.mdx)
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="unreal">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="unreal" />
This page provides a step-by-step guide on how to create a basic Interactive Live Streaming app using the Agora Video SDK.
## Understand the tech [#understand-the-tech-10]
To start a Interactive Live Streaming session, implement the following steps in your app:
* **Initialize the Agora Engine**: Before calling other APIs, create and initialize an Agora Engine instance.
* **Join a channel**: Call methods to create and join a channel.
* **Join as a host**: A live streaming event has one or more hosts. A host publishes audio and video to the channel. Hosts can also subscribe to streams from other hosts.
* **Join as audience**: Audience members can only subscribe to streams published by hosts.
* **Send and receive audio and video**: Hosts publish streams to the channel. Audience members subscribe to audio and video streams published by hosts.

## Prerequisites [#prerequisites-10]
* 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 only supports Unreal Engine 4 and below. To use Unreal Engine with 32-bit Windows, uncomment the code relating to `Win32` in the `AgoraPluginLibrary.Build.cs` file. |
* Two physical devices for testing
* A camera and a microphone
* A valid Agora account and project. Please refer to [Agora account management](manage-agora-account.md) for details.
## Set up your project [#set-up-your-project-10]
This section shows you how to set up your Unreal Engine project and install the Agora Video SDK.
**Create a new project**
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 **C++**.
* **Target Platform**: Select **Desktop**.
* **Project Location**: Enter the project files storage path.
* **Project Name**: Type a suitable name for your project.
Click **Create**.

**Add to an existing project**
1. In the Unreal Project Browser, click on **Browse** and locate the `.uproject` file.
2. Select the project and click **Open**.
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"
});
```
1. 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.
2. Initialize custom Widget
Add the following code to your widget header file:
```cpp
protected:
// Initialize custom Widget
void NativeConstruct() override;
```
3. 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**.

4. 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 Unreal Engine Video SDK to your project:
1. Download the latest version of Agora Unreal Video SDK from [Download SDKs](/en/api-reference/sdks?product=video\&platform=unreal-engine) and unzip it.
2. In your project root folder, create a `Plugins` folder.
3. Copy `AgoraPlugin` from the Unreal SDK folder to `Plugins`.
## Implement Interactive Live Streaming [#implement-interactive-live-streaming-10]
This section guides you through the implementation of basic real-time audio and video interaction in your game.
The following figure illustrates the essential steps:

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](manage-agora-account.md), and custom [event handler](#subscribe-to--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](build/authenticate-users/deploy-token-server.mdx) in your security infrastructure. For the purpose of this guide [Generate a temporary token](manage-agora-account.md).
* **User ID**: A 32-bit signed integer that identifies a user in the channel. You can specify a unique user ID for each user yourself. If you set the user ID to `0` when joining a channel, the SDK generates a random number for the user ID and returns the value in the `onJoinChannelSuccess` callback.
* **Channel media options**: Configure `ChannelMediaOptions` to define publishing and subscription settings, optimize performance for your specific use-case, and set optional parameters.
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 Interactive Live Streaming, set the `channelProfile` to `CHANNEL_PROFILE_LIVE_BROADCASTING`, the `clientRoleType` to `CLIENT_ROLE_BROADCASTER` (host) or `CLIENT_ROLE_AUDIENCE`, and the `audienceLatencyLevel` to `AUDIENCE_LATENCY_LEVEL_ULTRA_LOW_LATENCY`.
```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_LIVE_BROADCASTING;
// Set the user role as host or audience
options.clientRoleType = agora::rtc::CLIENT_ROLE_TYPE::CLIENT_ROLE_BROADCASTER;
// Set the latency level for optimal experience
options.audienceLatencyLevel = agora::rtc::AUDIENCE_LATENCY_LEVEL_TYPE::AUDIENCE_LATENCY_LEVEL_ULTRA_LOW_LATENCY;
// 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 camera and microphone, follow the steps for your target platform:
**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 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
}
```
**iOS and macOS**
1. Refer to [How to add the permissions required for real-time interaction to an Unreal Engine project?](/en/realtime-media/interactive-live-streaming/quickstart)
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" } );
}
}
```
### 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 game, 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`**
```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`**
```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_LIVE_BROADCASTING;
options.clientRoleType = agora::rtc::CLIENT_ROLE_TYPE::CLIENT_ROLE_BROADCASTER;
options.audienceLatencyLevel = agora::rtc::AUDIENCE_LATENCY_LEVEL_TYPE::AUDIENCE_LATENCY_LEVEL_ULTRA_LOW_LATENCY;
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`**
```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
}
}
```
### 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**
1. Create **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.

2. Create a view canvas.
Select **Palette > PANEL > Canvas Panel** and drag it to **AgoraWidget**.

3. Create join and leave channel buttons in Widget Blueprint
4. 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`
5. 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.
6. Repeat the above steps to create a **LeaveBtn**. Adjust the button size and position according to your layout design.
7. Create local and remote views in the Widget Blueprint
8. Select **COMMON > Image**, drag it to the **Canvas Panel**, 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`
9. 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`
10. Save the changes. Your user interface in **Widget Blueprint** looks similar to the following:

11. Create a **Level Blueprint** and associate it with the created **Widget Blueprint**
12. In **Unreal Editor**, click **Content Drawer**, right-click to select **Level**, and name it **agoraLevel**.
13. Double-click to open **agoraLevel** and click **Open Level Blueprint**.

14. Right-click and enter **Create Widget** in the search box. Select the created **AgoraWidget**, create **Event BeginPlay** and **Add to Viewport** in the same way. Connect them as follows:

15. Save your changes and run the project. The UI you have created looks similar to the following:

## 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 game 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 to this product.
* If a firewall is deployed in your network environment, refer to [Connect with Cloud Proxy](build/optimize-quality-and-connection/cloud-proxy.mdx) to use Agora services normally.
### Next steps [#next-steps-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](build/authenticate-users/use-tokens.mdx).
### 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/quickstart).
### API reference [#api-reference-10]
* [`JoinChannel`](https://api-ref.agora.io/en/video-sdk/unreal-engine/4.x/API/class_irtcengine.html#api_irtcengine_joinchannel)
* [`EnableVideo`](https://api-ref.agora.io/en/video-sdk/unreal-engine/4.x/API/class_irtcengine.html#api_irtcengine_enablevideo)
* [`CreateAgoraRtcEngine`](https://api-ref.agora.io/en/video-sdk/unreal-engine/4.x/API/class_irtcengine.html#api_createagorartcengine)
* [`SetClientRole`](https://api-ref.agora.io/en/video-sdk/unreal-engine/4.x/API/class_irtcengine.html#api_irtcengine_setclientrole)
* [`LeaveChannel`](https://api-ref.agora.io/en/video-sdk/unreal-engine/4.x/API/class_irtcengine.html#api_irtcengine_leavechannel)
* [`DisableVideo`](https://api-ref.agora.io/en/video-sdk/unreal-engine/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)
* [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-10]
* [Error codes](/en/realtime-media/video/reference/error-codes)
* [Connection status management](build/optimize-quality-and-connection/connection-status-management.mdx)
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="blueprint">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="blueprint" />
This page provides a step-by-step guide on how to create a basic Interactive Live Streaming app using the Agora Video SDK.
## Understand the tech [#understand-the-tech-11]
To start a Interactive Live Streaming session, implement the following steps in your app:
* **Initialize the Agora Engine**: Before calling other APIs, create and initialize an Agora Engine instance.
* **Join a channel**: Call methods to create and join a channel.
* **Join as a host**: A live streaming event has one or more hosts. A host publishes audio and video to the channel. Hosts can also subscribe to streams from other hosts.
* **Join as audience**: Audience members can only subscribe to streams published by hosts.
* **Send and receive audio and video**: Hosts publish streams to the channel. Audience members subscribe to audio and video streams published by hosts.

## Prerequisites [#prerequisites-11]
* 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 only supports Unreal Engine 4 and below. To use Unreal Engine with 32-bit Windows, uncomment the code relating to `Win32` in the `AgoraPluginLibrary.Build.cs` file. |
* Two physical devices for testing
* A camera and a microphone
* A valid Agora account and project. Please refer to [Agora account management](manage-agora-account.md) for details.
## Set up your project [#set-up-your-project-11]
This section shows you how to set up your Unreal (Blueprint) project and install the Agora Video SDK.
**Create a new project**
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**.

**Add to an existing project**
1. In the Unreal Project Browser, click on **Browse** and locate the `.uproject` file.
2. Select the project and click **Open**.
### Install the SDK [#install-the-sdk-11]
Take the following steps to add the Unreal (Blueprint) Video SDK to your project:
1. Download the latest version of Agora Unreal Video SDK from [Download SDKs](/en/api-reference/sdks?product=video\&platform=unreal-engine) and unzip it.
2. In your project root folder, create a `Plugins` folder.
3. Copy `AgoraPlugin` from the Unreal SDK folder to `Plugins`.
## Implement Interactive Live Streaming [#implement-interactive-live-streaming-11]
This section guides you through the implementation of basic real-time audio and video interaction in your game.
### 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.

### 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 Interactive Live Streaming logic:

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. Note: This node is available on Windows and macOS only. If the node is not retrieved at creation time, uncheck **Context Sensitive**.  |
| 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. Note: If your target platform is Android, create this node to check system permissions. |
| 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.
\*\* 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**.
1. 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.

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

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)
1. To initialize the RTC engine, in the **InitRtcEngine** function, create and connect nodes as shown in the following figure:

2. 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.
3. Bind `IRtcEngineEventHandler` class-related callback functions.
4. 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`. |
1. Create a **Bind Event** function. In this function, add a **Sequence** node, and then bind the `onJoinChannelSuccess`, `onLeaveChannel`, `onUserJoined`, and `onUserOffline` callback events.

2. `IRtcEngine` initialization
3. Call **Initialize** to initialize the RTC engine.
4. Connect to the **RtcEngineContext** configuration `IRtcEngine` instance and select **Channel Profile** as `CHANNEL_PROFILE_LIVE_BROADCASTING`.
### Bind UI events [#bind-ui-events]
To bind UI events:
1. Create and implement the `OnJoinChannelClicked` event callback.

2. Call **Enable Video** and **Enable Audio** to enable the video and audio modules.
3. Call **Join Channel** to join the channel.
4. 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`.
5. Create and implement the `OnLeaveChannelClicked` event callback. When the event is triggered, call **Leave Channel** to leave the channel.

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

You can also bind UI events in Unreal Motion Graphics (UMG). This document only shows binding using the **Bind UIEvent Function**.
### 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:

2. 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.
3. 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).
4. 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).
5. Create and implement the **ReleaseVideoView** function to release the view when a local or remote user leaves the channel.

6. 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.
7. Release the local view.
8. 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):

2. After the local user leaves the channel, the `onLeaveChannel` callback is triggered which releases the local view:

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):

4. When a remote user leaves the channel, the `onUserOffline` callback is triggered to release the remote view:

### 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 SDRTN® event callback.
3. Destroy `IRtcEngineObject` to release all resources used by Agora SDK.
Refer to the figure below to implement the `UnInitRtcEngine` function:

## 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 game 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 to this product.
* If a firewall is deployed in your network environment, refer to [Connect with Cloud Proxy](build/optimize-quality-and-connection/cloud-proxy.mdx) to use Agora services normally.
### Next steps [#next-steps-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](build/authenticate-users/use-tokens.mdx).
### 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-9]
* [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)
* [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-11]
* [Error codes](/en/realtime-media/video/reference/error-codes)
* [Connection status management](build/optimize-quality-and-connection/connection-status-management.mdx)
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="python">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="python" />
This page provides a step-by-step guide on how to create a basic Interactive Live Streaming app using the Agora Video SDK.
## Understand the tech [#understand-the-tech-12]
To start a Interactive Live Streaming session, implement the following steps in your app:
* **Initialize the Agora Engine**: Before calling other APIs, create and initialize an Agora Engine instance.
* **Join a channel**: Call methods to create and join a channel.
* **Join as a host**: A live streaming event has one or more hosts. A host publishes audio and video to the channel. Hosts can also subscribe to streams from other hosts.
* **Join as audience**: Audience members can only subscribe to streams published by hosts.
* **Send and receive audio and video**: Hosts publish streams to the channel. Audience members subscribe to audio and video streams published by hosts.

## Prerequisites [#prerequisites-12]
* A camera and a microphone
* A valid Agora account and project. Please refer to [Agora account management](manage-agora-account.md) for details.
## Set up your project [#set-up-your-project-12]
This section shows you how to set up your Python project and install the Agora Video SDK.
1. Install the build tools for compiling the SDK.
```text
sudo apt install build-essential python3-dev
```
2. To implement structured, asynchronous event handling in Python, install the `pyee` library.
```bash
pip3 install pyee
```
3. Install the Agora server side Python SDK.
```bash
pip3 install agora-python-server-sdk
```
The Python SDK is a server side SDK.
## Implement Interactive Live Streaming [#implement-interactive-live-streaming-12]
This section guides you through the implementation of basic real-time audio and video interaction in your app.
### Import Agora classes [#import-agora-classes-2]
Import the relevant Agora SDK classes and interfaces:
```python
from agora.rtc.agora_base import (
AudioScenarioType,
ChannelProfileType,
ClientRoleType,
)
from agora.rtc.agora_service import (
AgoraService,
AgoraServiceConfig,
RTCConnConfig,
)
from agora.rtc.audio_frame_observer import AudioFrame, IAudioFrameObserver
from agora.rtc.audio_pcm_data_sender import PcmAudioFrame
from agora.rtc.local_user import LocalUser
from agora.rtc.local_user_observer import IRTCLocalUserObserver
from agora.rtc.rtc_connection import RTCConnection, RTCConnInfo
from agora.rtc.rtc_connection_observer import IRTCConnectionObserver
```
### Initialize the engine [#initialize-the-engine-9]
The following code defines the `RtcEngine` class, which initializes and configures the `AgoraService`. The class constructor takes an `appid` as input, configures the Agora service, and initializes it. You use the `RtcEngine` class to interact with the Agora SDK in this demo.
```python
class RtcEngine:
def __init__(self, appid: str, appcert: str):
self.appid = appid
self.appcert = appcert
if not appid:
raise Exception("App ID is required)")
config = AgoraServiceConfig()
config.audio_scenario = AudioScenarioType.AUDIO_SCENARIO_CHORUS
config.appid = appid
config.log_path = os.path.join(
os.path.dirname(
os.path.dirname(
os.path.dirname(os.path.join(os.path.abspath(__file__)))
)
),
"agorasdk.log",
)
self.agora_service = AgoraService()
self.agora_service.initialize(config)
```
### Join a channel [#join-a-channel-11]
To asynchronously join a channel, implement a `Channel` class. When you create an instance of the class, the initializer sets up the necessary components for joining a channel. It takes an instance of `RtcEngine`, a `channelId`, and a `uid` as parameters. During initialization, the code creates an event emitter, configures the connection for broadcasting, and registers an event observer for channel events. It also sets up the local user’s audio configuration to enable audio streaming.
UIDs in the Python SDK are set using a string value. Agora recommends using only numerical values for UID strings to ensure compatibility with all Agora products and extensions.
```python
class Channel:
def __init__(self, rtc: "RtcEngine", options: RtcOptions) -> None:
self.loop = asyncio.get_event_loop()
# Create the event emitter
self.emitter = AsyncIOEventEmitter(self.loop)
self.connection_state = 0
self.options = options
self.remote_users = dict[int, Any]()
self.rtc = rtc
self.chat = Chat(self)
self.channelId = options.channel_name
self.uid = options.uid
self.enable_pcm_dump = options.enable_pcm_dump
self.token = options.build_token(rtc.appid, rtc.appcert) if rtc.appcert else ""
conn_config = RTCConnConfig(
client_role_type=ClientRoleType.CLIENT_ROLE_BROADCASTER,
channel_profile=ChannelProfileType.CHANNEL_PROFILE_LIVE_BROADCASTING,
)
self.connection = self.rtc.agora_service.create_rtc_connection(conn_config)
self.channel_event_observer = ChannelEventObserver(
self.emitter,
options=options,
)
self.connection.register_observer(self.channel_event_observer)
self.local_user = self.connection.get_local_user()
self.local_user.set_playback_audio_frame_before_mixing_parameters(
options.channels, options.sample_rate
)
self.local_user.register_local_user_observer(self.channel_event_observer)
self.local_user.register_audio_frame_observer(self.channel_event_observer)
# self.local_user.subscribe_all_audio()
self.media_node_factory = self.rtc.agora_service.create_media_node_factory()
self.audio_pcm_data_sender = (
self.media_node_factory.create_audio_pcm_data_sender()
)
self.audio_track = self.rtc.agora_service.create_custom_audio_track_pcm(
self.audio_pcm_data_sender
)
self.audio_track.set_enabled(1)
self.local_user.publish_audio(self.audio_track)
self.stream_id = self.connection.create_data_stream(False, False)
self.received_chunks = {}
self.waiting_message = None
self.msg_id = ""
self.msg_index = ""
self.on(
"user_joined",
lambda agora_rtc_conn, user_id: self.remote_users.update({user_id: True}),
)
self.on(
"user_left",
lambda agora_rtc_conn, user_id, reason: self.remote_users.pop(
user_id, None
),
)
```
The following code uses the `Channel` class to join a channel. It sets up a `future` to handle the connection state and returns a `Channel` object when the connection is successfully established.
```python
async def connect(self) -> None:
"""
Connects to a channel.
Parameters:
channelId: The channel ID.
uid: The user ID.
Returns:
Channel: The connected channel.
"""
if self.connection_state == 3:
return
future = asyncio.Future()
def callback(agora_rtc_conn: RTCConnection, conn_info: RTCConnInfo, reason):
logger.info(f"Connection state changed: {conn_info.state}")
if conn_info.state == 3: # Connection successful
future.set_result(None)
elif conn_info.state == 5: # Connection failed
future.set_exception(
Exception(f"Connection failed with state: {conn_info.state}")
)
self.on("connection_state_changed", callback)
logger.info(f"Connecting to channel {self.channelId} with token {self.token}")
self.connection.connect(self.token, self.channelId, f"{self.uid}")
if self.enable_pcm_dump:
agora_parameter = self.connection.get_agora_parameter()
agora_parameter.set_parameters("{\"che.audio.frame_dump\":{\"location\":\"all\",\"action\":\"start\",\"max_size_bytes\":\"120000000\",\"uuid\":\"123456789\",\"duration\":\"1200000\"}}")
try:
await future
except Exception as e:
raise Exception(
f"Failed to connect to channel {self.channelId}: {str(e)}"
) from e
finally:
self.off("connection_state_changed", callback)
```
### Handle connection and channel events [#handle-connection-and-channel-events]
To listen for channel and connection events, such as users joining or leaving the channel, and connection state changes, implement the `ChannelEventObserver` class. This class enables you to respond to SDK events.
```python
class ChannelEventObserver(
IRTCConnectionObserver, IRTCLocalUserObserver, IAudioFrameObserver
):
def __init__(self, event_emitter: AsyncIOEventEmitter, options: RtcOptions) -> None:
self.loop = asyncio.get_event_loop()
self.emitter = event_emitter
self.audio_streams = dict[int, AudioStream]()
self.options = options
def emit_event(self, event_name: str, *args):
"""Helper function to emit events."""
self.loop.call_soon_threadsafe(self.emitter.emit, event_name, *args)
def on_connected(
self, agora_rtc_conn: RTCConnection, conn_info: RTCConnInfo, reason
):
logger.info(f"Connected to RTC: {agora_rtc_conn} {conn_info} {reason}")
self.emit_event("connection_state_changed", agora_rtc_conn, conn_info, reason)
def on_disconnected(
self, agora_rtc_conn: RTCConnection, conn_info: RTCConnInfo, reason
):
logger.info(f"Disconnected from RTC: {agora_rtc_conn} {conn_info} {reason}")
self.emit_event("connection_state_changed", agora_rtc_conn, conn_info, reason)
def on_connecting(
self, agora_rtc_conn: RTCConnection, conn_info: RTCConnInfo, reason
):
logger.info(f"Connecting to RTC: {agora_rtc_conn} {conn_info} {reason}")
self.emit_event("connection_state_changed", agora_rtc_conn, conn_info, reason)
def on_connection_failure(self, agora_rtc_conn, conn_info, reason):
logger.error(f"Connection failure: {agora_rtc_conn} {conn_info} {reason}")
self.emit_event("connection_state_changed", agora_rtc_conn, conn_info, reason)
def on_user_joined(self, agora_rtc_conn: RTCConnection, user_id):
logger.info(f"User joined: {agora_rtc_conn} {user_id}")
self.emit_event("user_joined", agora_rtc_conn, user_id)
def on_user_left(self, agora_rtc_conn: RTCConnection, user_id, reason):
logger.info(f"User left: {agora_rtc_conn} {user_id} {reason}")
self.emit_event("user_left", agora_rtc_conn, user_id, reason)
def handle_received_chunk(self, json_chunk):
chunk = json.loads(json_chunk)
msg_id = chunk["msg_id"]
part_idx = chunk["part_idx"]
total_parts = chunk["total_parts"]
if msg_id not in self.received_chunks:
self.received_chunks[msg_id] = {"parts": {}, "total_parts": total_parts}
if (
part_idx not in self.received_chunks[msg_id]["parts"]
and 0 <= part_idx < total_parts
):
self.received_chunks[msg_id]["parts"][part_idx] = chunk
if len(self.received_chunks[msg_id]["parts"]) == total_parts:
# all parts received, now recomposing original message and get rid it from dict
sorted_parts = sorted(
self.received_chunks[msg_id]["parts"].values(),
key=lambda c: c["part_idx"],
)
full_message = "".join(part["content"] for part in sorted_parts)
del self.received_chunks[msg_id]
return full_message, msg_id
return (None, None)
def on_stream_message(
self, agora_local_user: LocalUser, user_id, stream_id, data, length
):
# logger.info(f"Stream message", agora_local_user, user_id, stream_id, length)
(reassembled_message, msg_id) = self.handle_received_chunk(data)
if reassembled_message is not None:
logger.info(f"Reassembled message: {msg_id} {reassembled_message}")
def on_audio_subscribe_state_changed(
self,
agora_local_user,
channel,
user_id,
old_state,
new_state,
elapse_since_last_state,
):
logger.info(
f"Audio subscribe state changed: {user_id} {new_state} {elapse_since_last_state}"
)
self.emit_event(
"audio_subscribe_state_changed",
agora_local_user,
channel,
user_id,
old_state,
new_state,
elapse_since_last_state,
)
def on_playback_audio_frame_before_mixing(
self, agora_local_user: LocalUser, channelId, uid, frame: AudioFrame
):
audio_frame = PcmAudioFrame()
audio_frame.samples_per_channel = frame.samples_per_channel
audio_frame.bytes_per_sample = frame.bytes_per_sample
audio_frame.number_of_channels = frame.channels
audio_frame.sample_rate = self.options.sample_rate
audio_frame.data = frame.buffer
self.loop.call_soon_threadsafe(
self.audio_streams[uid].queue.put_nowait, audio_frame
)
return 0
```
### Subscribe to an audio stream [#subscribe-to-an-audio-stream]
To asynchronously subscribe to audio streams for a specific user identified by their `uid`, refer to the following code. The method sets up a callback to monitor changes in the audio subscription state and handles the result based on whether the subscription is successfully established.
```python
async def subscribe_audio(self, uid: int) -> None:
"""
Subscribes to the audio of a user.
Parameters:
uid: The user ID to subscribe to.
"""
future = asyncio.Future()
def callback(
agora_local_user,
channel,
user_id,
old_state,
new_state,
elapse_since_last_state,
):
if new_state == 3: # Successfully subscribed
future.set_result(None)
self.on("audio_subscribe_state_changed", callback)
self.local_user.subscribe_audio(uid)
try:
await future
except Exception as e:
raise Exception(
f"Audio subscription failed for user {uid}: {str(e)}"
) from e
finally:
self.off("audio_subscribe_state_changed", callback)
```
### Unsubscribe from an audio stream [#unsubscribe-from-an-audio-stream]
To unsubscribe from an audio stream, implement an asynchronous method similar to `subscribe_audio` and use the following code to unsubscribe:
```python
self.local_user.unsubscribe_audio(uid)
```
### Disconnect from the service [#disconnect-from-the-service]
To leave a channel, disconnect from Agora SDRTN® and release resources, refer to he following code.
```python
async def disconnect(self) -> None:
"""
Disconnects the channel.
"""
if self.connection_state == 1:
return
disconnected_future = asyncio.Future[None]()
def callback(agora_rtc_conn: RTCConnection, conn_info: RTCConnInfo, reason):
self.off("connection_state_changed", callback)
if conn_info.state == 1:
disconnected_future.set_result(None)
self.on("connection_state_changed", callback)
self.connection.disconnect()
await disconnected_future
```
### Complete code [#complete-code]
The `rtc.py` script integrates the code components presented in this section into reusable Python classes that you can extend for your own applications.
**Complete code for `rtc.py`**
```python
import asyncio
import json
import logging
import os
from typing import Any, AsyncIterator
from agora.rtc.agora_base import (
AudioScenarioType,
ChannelProfileType,
ClientRoleType,
)
from agora.rtc.agora_service import (
AgoraService,
AgoraServiceConfig,
RTCConnConfig,
)
from agora.rtc.audio_frame_observer import AudioFrame, IAudioFrameObserver
from agora.rtc.audio_pcm_data_sender import PcmAudioFrame
from agora.rtc.local_user import LocalUser
from agora.rtc.local_user_observer import IRTCLocalUserObserver
from agora.rtc.rtc_connection import RTCConnection, RTCConnInfo
from agora.rtc.rtc_connection_observer import IRTCConnectionObserver
from pyee.asyncio import AsyncIOEventEmitter
from .logger import setup_logger
from .token_builder.realtimekit_token_builder import RealtimekitTokenBuilder
# Set up the logger with color and timestamp support
logger = setup_logger(name=__name__, log_level=logging.INFO)
class RtcOptions:
def __init__(
self,
*,
channel_name: str = None,
uid: int = 0,
sample_rate: int = 24000,
channels: int = 1,
enable_pcm_dump: bool = False,
):
self.channel_name = channel_name
self.uid = uid
self.sample_rate = sample_rate
self.channels = channels
self.enable_pcm_dump = enable_pcm_dump
def build_token(self, appid: str, appcert: str) -> str:
return RealtimekitTokenBuilder.build_token(
appid, appcert, self.channel_name, self.uid
)
class AudioStream:
def __init__(self) -> None:
self.queue: asyncio.Queue = asyncio.Queue()
def __aiter__(self) -> AsyncIterator[PcmAudioFrame]:
return self
async def __anext__(self) -> PcmAudioFrame:
item = await self.queue.get()
if item is None:
raise StopAsyncIteration
return item
class ChannelEventObserver(
IRTCConnectionObserver, IRTCLocalUserObserver, IAudioFrameObserver
):
def __init__(self, event_emitter: AsyncIOEventEmitter, options: RtcOptions) -> None:
self.loop = asyncio.get_event_loop()
self.emitter = event_emitter
self.audio_streams = dict[int, AudioStream]()
self.options = options
def emit_event(self, event_name: str, *args):
"""Helper function to emit events."""
self.loop.call_soon_threadsafe(self.emitter.emit, event_name, *args)
def on_connected(
self, agora_rtc_conn: RTCConnection, conn_info: RTCConnInfo, reason
):
logger.info(f"Connected to RTC: {agora_rtc_conn} {conn_info} {reason}")
self.emit_event("connection_state_changed", agora_rtc_conn, conn_info, reason)
def on_disconnected(
self, agora_rtc_conn: RTCConnection, conn_info: RTCConnInfo, reason
):
logger.info(f"Disconnected from RTC: {agora_rtc_conn} {conn_info} {reason}")
self.emit_event("connection_state_changed", agora_rtc_conn, conn_info, reason)
def on_connecting(
self, agora_rtc_conn: RTCConnection, conn_info: RTCConnInfo, reason
):
logger.info(f"Connecting to RTC: {agora_rtc_conn} {conn_info} {reason}")
self.emit_event("connection_state_changed", agora_rtc_conn, conn_info, reason)
def on_connection_failure(self, agora_rtc_conn, conn_info, reason):
logger.error(f"Connection failure: {agora_rtc_conn} {conn_info} {reason}")
self.emit_event("connection_state_changed", agora_rtc_conn, conn_info, reason)
def on_user_joined(self, agora_rtc_conn: RTCConnection, user_id):
logger.info(f"User joined: {agora_rtc_conn} {user_id}")
self.emit_event("user_joined", agora_rtc_conn, user_id)
def on_user_left(self, agora_rtc_conn: RTCConnection, user_id, reason):
logger.info(f"User left: {agora_rtc_conn} {user_id} {reason}")
self.emit_event("user_left", agora_rtc_conn, user_id, reason)
def handle_received_chunk(self, json_chunk):
chunk = json.loads(json_chunk)
msg_id = chunk["msg_id"]
part_idx = chunk["part_idx"]
total_parts = chunk["total_parts"]
if msg_id not in self.received_chunks:
self.received_chunks[msg_id] = {"parts": {}, "total_parts": total_parts}
if (
part_idx not in self.received_chunks[msg_id]["parts"]
and 0 <= part_idx < total_parts
):
self.received_chunks[msg_id]["parts"][part_idx] = chunk
if len(self.received_chunks[msg_id]["parts"]) == total_parts:
# all parts received, now recomposing original message and get rid it from dict
sorted_parts = sorted(
self.received_chunks[msg_id]["parts"].values(),
key=lambda c: c["part_idx"],
)
full_message = "".join(part["content"] for part in sorted_parts)
del self.received_chunks[msg_id]
return full_message, msg_id
return (None, None)
def on_stream_message(
self, agora_local_user: LocalUser, user_id, stream_id, data, length
):
# logger.info(f"Stream message", agora_local_user, user_id, stream_id, length)
(reassembled_message, msg_id) = self.handle_received_chunk(data)
if reassembled_message is not None:
logger.info(f"Reassembled message: {msg_id} {reassembled_message}")
def on_audio_subscribe_state_changed(
self,
agora_local_user,
channel,
user_id,
old_state,
new_state,
elapse_since_last_state,
):
logger.info(
f"Audio subscribe state changed: {user_id} {new_state} {elapse_since_last_state}"
)
self.emit_event(
"audio_subscribe_state_changed",
agora_local_user,
channel,
user_id,
old_state,
new_state,
elapse_since_last_state,
)
def on_playback_audio_frame_before_mixing(
self, agora_local_user: LocalUser, channelId, uid, frame: AudioFrame
):
audio_frame = PcmAudioFrame()
audio_frame.samples_per_channel = frame.samples_per_channel
audio_frame.bytes_per_sample = frame.bytes_per_sample
audio_frame.number_of_channels = frame.channels
audio_frame.sample_rate = self.options.sample_rate
audio_frame.data = frame.buffer
# print(
# "on_playback_audio_frame_before_mixing",
# audio_frame.samples_per_channel,
# audio_frame.bytes_per_sample,
# audio_frame.number_of_channels,
# audio_frame.sample_rate,
# len(audio_frame.data),
# )
self.loop.call_soon_threadsafe(
self.audio_streams[uid].queue.put_nowait, audio_frame
)
return 0
class Channel:
def __init__(self, rtc: "RtcEngine", options: RtcOptions) -> None:
self.loop = asyncio.get_event_loop()
# Create the event emitter
self.emitter = AsyncIOEventEmitter(self.loop)
self.connection_state = 0
self.options = options
self.remote_users = dict[int, Any]()
self.rtc = rtc
self.chat = Chat(self)
self.channelId = options.channel_name
self.uid = options.uid
self.enable_pcm_dump = options.enable_pcm_dump
self.token = options.build_token(rtc.appid, rtc.appcert) if rtc.appcert else ""
conn_config = RTCConnConfig(
client_role_type=ClientRoleType.CLIENT_ROLE_BROADCASTER,
channel_profile=ChannelProfileType.CHANNEL_PROFILE_LIVE_BROADCASTING,
)
self.connection = self.rtc.agora_service.create_rtc_connection(conn_config)
self.channel_event_observer = ChannelEventObserver(
self.emitter,
options=options,
)
self.connection.register_observer(self.channel_event_observer)
self.local_user = self.connection.get_local_user()
self.local_user.set_playback_audio_frame_before_mixing_parameters(
options.channels, options.sample_rate
)
self.local_user.register_local_user_observer(self.channel_event_observer)
self.local_user.register_audio_frame_observer(self.channel_event_observer)
# self.local_user.subscribe_all_audio()
self.media_node_factory = self.rtc.agora_service.create_media_node_factory()
self.audio_pcm_data_sender = (
self.media_node_factory.create_audio_pcm_data_sender()
)
self.audio_track = self.rtc.agora_service.create_custom_audio_track_pcm(
self.audio_pcm_data_sender
)
self.audio_track.set_enabled(1)
self.local_user.publish_audio(self.audio_track)
self.stream_id = self.connection.create_data_stream(False, False)
self.received_chunks = {}
self.waiting_message = None
self.msg_id = ""
self.msg_index = ""
self.on(
"user_joined",
lambda agora_rtc_conn, user_id: self.remote_users.update({user_id: True}),
)
self.on(
"user_left",
lambda agora_rtc_conn, user_id, reason: self.remote_users.pop(
user_id, None
),
)
def handle_audio_subscribe_state_changed(
agora_local_user,
channel,
user_id,
old_state,
new_state,
elapse_since_last_state,
):
if new_state == 3: # Successfully subscribed
self.channel_event_observer.audio_streams.update(
{user_id: AudioStream()}
)
elif new_state == 0:
self.channel_event_observer.audio_streams.pop(user_id, None)
self.on("audio_subscribe_state_changed", handle_audio_subscribe_state_changed)
self.on(
"connection_state_changed",
lambda agora_rtc_conn, conn_info, reason: setattr(
self, "connection_state", conn_info.state
),
)
async def connect(self) -> None:
"""
Connects to a channel.
Parameters:
channelId: The channel ID.
uid: The user ID.
Returns:
Channel: The connected channel.
"""
if self.connection_state == 3:
return
future = asyncio.Future()
def callback(agora_rtc_conn: RTCConnection, conn_info: RTCConnInfo, reason):
logger.info(f"Connection state changed: {conn_info.state}")
if conn_info.state == 3: # Connection successful
future.set_result(None)
elif conn_info.state == 5: # Connection failed
future.set_exception(
Exception(f"Connection failed with state: {conn_info.state}")
)
self.on("connection_state_changed", callback)
logger.info(f"Connecting to channel {self.channelId} with token {self.token}")
self.connection.connect(self.token, self.channelId, f"{self.uid}")
if self.enable_pcm_dump:
agora_parameter = self.connection.get_agora_parameter()
agora_parameter.set_parameters("{"che.audio.frame_dump":{"location":"all","action":"start","max_size_bytes":"120000000","uuid":"123456789","duration":"1200000"}}")
try:
await future
except Exception as e:
raise Exception(
f"Failed to connect to channel {self.channelId}: {str(e)}"
) from e
finally:
self.off("connection_state_changed", callback)
async def disconnect(self) -> None:
"""
Disconnects the channel.
"""
if self.connection_state == 1:
return
disconnected_future = asyncio.Future[None]()
def callback(agora_rtc_conn: RTCConnection, conn_info: RTCConnInfo, reason):
self.off("connection_state_changed", callback)
if conn_info.state == 1:
disconnected_future.set_result(None)
self.on("connection_state_changed", callback)
self.connection.disconnect()
await disconnected_future
def get_audio_frames(self, uid: int) -> AudioStream:
"""
Returns the audio frames from the channel.
Returns:
AudioStream: The audio stream.
"""
return self.channel_event_observer.audio_streams[uid]
async def push_audio_frame(self, frame: bytes) -> None:
"""
Pushes an audio frame to the channel.
Parameters:
frame: The audio frame to push.
"""
audio_frame = PcmAudioFrame()
audio_frame.data = bytearray(frame)
audio_frame.timestamp = 0
audio_frame.bytes_per_sample = 2
audio_frame.number_of_channels = self.options.channels
audio_frame.sample_rate = self.options.sample_rate
audio_frame.samples_per_channel = int(
len(frame) / audio_frame.bytes_per_sample / audio_frame.number_of_channels
)
ret = self.audio_pcm_data_sender.send_audio_pcm_data(audio_frame)
logger.info(f"Pushed audio frame: {ret}, audio frame length: {len(frame)}")
if ret < 0:
raise Exception(f"Failed to send audio frame: {ret}, audio frame length: {len(frame)}")
async def clear_sender_audio_buffer(self) -> None:
"""
Clears the audio buffer which is used to send.
"""
self.audio_track.clear_sender_buffer()
async def subscribe_audio(self, uid: int) -> None:
"""
Subscribes to the audio of a user.
Parameters:
uid: The user ID to subscribe to.
"""
future = asyncio.Future()
def callback(
agora_local_user,
channel,
user_id,
old_state,
new_state,
elapse_since_last_state,
):
if new_state == 3: # Successfully subscribed
future.set_result(None)
# elif new_state == 1: # Subscription failed
# future.set_exception(
# Exception(
# f"Failed to subscribe {user_id} audio: state changed from {old_state} to {new_state}"
# )
# )
self.on("audio_subscribe_state_changed", callback)
self.local_user.subscribe_audio(uid)
try:
await future
except Exception as e:
raise Exception(
f"Audio subscription failed for user {uid}: {str(e)}"
) from e
finally:
self.off("audio_subscribe_state_changed", callback)
async def unsubscribe_audio(self, uid: int) -> None:
"""
Unsubscribes from the audio of a user.
Parameters:
uid: The user ID to unsubscribe from.
"""
future = asyncio.Future()
def callback(
agora_local_user,
channel,
user_id,
old_state,
new_state,
elapse_since_last_state,
):
if new_state == 3: # Successfully unsubscribed
future.set_result(None)
else: # Failed to unsubscribe
future.set_exception(
Exception(
f"Failed to unsubscribe {user_id} audio: state changed from {old_state} to {new_state}"
)
)
self.on("audio_subscribe_state_changed", callback)
self.local_user.unsubscribe_audio(uid)
try:
await future
except Exception as e:
raise Exception(
f"Audio unsubscription failed for user {uid}: {str(e)}"
) from e
finally:
self.off("audio_subscribe_state_changed", callback)
def _split_string_into_chunks(
self, long_string, msg_id, chunk_size=300
) -> list[dict[str:Any]]:
"""
Splits a long string into chunks of a given size.
Parameters:
long_string: The string to split.
msg_id: The message ID.
chunk_size: The size of each chunk.
Returns:
list[dict[str: Any]]: The list of chunks.
"""
total_parts = (len(long_string) + chunk_size - 1) // chunk_size
json_chunks = []
for idx in range(total_parts):
start = idx * chunk_size
end = min(start + chunk_size, len(long_string))
chunk = {
"msg_id": msg_id,
"part_idx": idx,
"total_parts": total_parts,
"content": long_string[start:end],
}
json_chunk = json.dumps(chunk, ensure_ascii=False)
json_chunks.append(json_chunk)
return json_chunks
async def send_stream_message(self, data: str, msg_id: str) -> None:
"""
Sends a stream message to the channel.
Parameters:
data: The data to send.
msg_id: The message ID.
"""
chunks = self._split_string_into_chunks(data, msg_id)
for chunk in chunks:
self.connection.send_stream_message(self.stream_id, chunk)
def on(self, event_name: str, callback):
"""
Allows external components to subscribe to events.
Parameters:
event_name: The name of the event to subscribe to.
callback: The callback to call when the event is emitted.
"""
self.emitter.on(event_name, callback)
def once(self, event_name: str, callback):
"""
Allows external components to subscribe to events once.
Parameters:
event_name: The name of the event to subscribe to.
callback: The callback to call when the event is emitted.
"""
self.emitter.once(event_name, callback)
def off(self, event_name: str, callback):
"""
Allows external components to unsubscribe from events.
Parameters:
event_name: The name of the event to unsubscribe from.
callback: The callback to remove from the event.
"""
self.emitter.remove_listener(event_name, callback)
class ChatMessage:
def __init__(self, message: str, msg_id: str) -> None:
self.message = message
self.msg_id = msg_id
class Chat:
def __init__(self, channel: Channel) -> None:
self.channel = channel
self.loop = self.channel.loop
self.queue = asyncio.Queue()
def log_exception(t: asyncio.Task[Any]) -> None:
if not t.cancelled() and t.exception():
logger.error(
"unhandled exception",
exc_info=t.exception(),
)
asyncio.create_task(self._process_message()).add_done_callback(log_exception)
async def send_message(self, item: ChatMessage) -> None:
"""
Sends a message to the channel.
Parameters:
item: The message to send.
"""
await self.queue.put(item)
# await self.queue.put_nowait(item)
async def _process_message(self) -> None:
"""
Processes messages in the queue.
"""
while True:
item: ChatMessage = await self.queue.get()
await self.channel.send_stream_message(item.message, item.msg_id)
self.queue.task_done()
# await asyncio.sleep(0)
class RtcEngine:
def __init__(self, appid: str, appcert: str):
self.appid = appid
self.appcert = appcert
if not appid:
raise Exception("App ID is required)")
config = AgoraServiceConfig()
config.audio_scenario = AudioScenarioType.AUDIO_SCENARIO_CHORUS
config.appid = appid
config.log_path = os.path.join(
os.path.dirname(
os.path.dirname(
os.path.dirname(os.path.join(os.path.abspath(__file__)))
)
),
"agorasdk.log",
)
self.agora_service = AgoraService()
self.agora_service.initialize(config)
def create_channel(self, options: RtcOptions) -> Channel:
"""
Creates a channel.
Parameters:
channelId: The channel ID.
uid: The user ID.
Returns:
Channel: The created channel.
"""
return Channel(self, options)
def destroy(self) -> None:
"""
Destroys the RTC engine.
"""
self.agora_service.release()
```
## Test the sample code [#test-the-sample-code-12]
Follow these steps to test the demo code:
1. Create a file named `rtc.py` and paste the [complete source code](#complete-code) into this file.
2. Create a file named `main.py` in the same folder as `rtc.py` and copy the following code to the file:
```python
import asyncio
from rtc import RtcEngine # Import the RtcEngine class from rtc.py
async def main():
appid = "" # Replace with your Agora App ID
channelId = "demo" # Replace with your desired channel ID
uid = "123" # Replace with your unique user ID
rtc_engine = RtcEngine(appid)
channel = await rtc_engine.connect(channelId, uid)
# Keep the script running to listen for events
await asyncio.Event().wait()
if __name__ == "__main__":
asyncio.run(main())
```
3. To specify the audio parameters, create a folder named `realtimeapi` and add a file `util.py` containing the following code:
```python
# Number of audio channels (1 for mono, 2 for stereo)
CHANNELS = 2
# Sample rate for audio processing (in Hz)
SAMPLE_RATE = 44100 # Common sample rates include 8000, 16000, 44100, 48000
```
4. To run the app, execute the following command in your terminal:
```bash
python3 main.py
```
You see output similar to the following:
```text
Initialization result: 0
LocalUserCB _on_audio_track_publish_start: 123456607304576 123456864576600
```
Congratulations! You have successfully connected to the Agora SDRTN® and joined a channel.
## Reference [#reference-12]
This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.
* If a firewall is deployed in your network environment, refer to [Connect with Cloud Proxy](build/optimize-quality-and-connection/cloud-proxy.mdx) to use Agora services normally.
### Next steps [#next-steps-12]
After implementing the quickstart sample, read the following documents to learn more:
* To ensure communication security in a test or production environment, best practice is to obtain and use a token from an authentication server. For details, see [Secure authentication with tokens](build/authenticate-users/use-tokens.mdx).
### API reference [#api-reference-11]
* [`rtc.py`](https://api-ref.agora.io/en/voice-sdk/python/rtc-py-api.html)
<_PlatformProcessedMarker close="true" />
# Subscription packages (/en/realtime-media/interactive-live-streaming/subscription-packages)
A subscription package is a prepaid billing method. You can purchase a package in the [`Agora Console`](https://console.agora.io/subscriptions/rtc-plans?tab=monthly) to cover usage for the current month.
This page explains how to purchase, and manage subscription and top-up packages.
### Purchase and upgrade [#purchase-and-upgrade]
By default, a new account is assigned the Free package when the first project is created. You can upgrade at any time to the Starter, Pro, Business, or Business Plus. Higher-tier packages provide greater discounts and lower unit prices. To upgrade:
* Log in to [Agora Console](https://console.agora.io/subscriptions/rtc-plans?tab=monthly).
* In the sidebar, click **Subscriptions**.
* Under **All Subscriptions**, click **RTC Prepaid Plans**.
* Select your package and click **Subscribe**.

- For non-contracted customers, all packages, except the Enterprise package can be purchased directly from the Agora Console. To upgrade to the Enterprise package, contact [Agora sales](mailto\:sales@agora.io).
- You can upgrade sequentially or skip levels. For example, you may upgrade from Starter to Business Plus directly.
- Package upgrades take effect immediately and apply to the entire calendar month. If you make multiple upgrades on the same day, only the last upgrade is applied.
- If your account has a negative balance, purchasing a prepaid package first charges your credit card for the outstanding balance, then for the package fee.
#### Upgrade rules [#upgrade-rules]
When you upgrade to a paid package, it becomes effective for the entire calendar month, regardless of the purchase date. You receive the full monthly allocation of included minutes. The cost of the previous package is deducted from the new package price, so you only pay the difference. If you request an upgrade after auto-renewal has triggered, you may upgrade for the next month as well.
### Renewal [#renewal]
By default, your subscription package renews automatically 48 hours before it expires. Ensure your account has sufficient balance or enable auto-pay to avoid renewal failure. You can unsubscribe at any time to prevent renewal. See [Cancel subscription](#cancel-subscription).
If renewal fails:
* The system sends a notification and retries the renewal.
* If renewal still fails by the time the package expires, the package downgrades to the Free package.
* To restore the previous package, see [Purchase and Upgrade](#purchase-and-upgrade).
### Downgrade [#downgrade]
You can downgrade by selecting and purchasing a lower-tier package. A downgrade takes effect when your current package expires if you request it before auto-renewal is triggered. You are charged for the new package when you request the downgrade.
You can downgrade only once per month.
* If your downgrade for the next month is successful, auto-renewal for the current package is not triggered.
* If a package is already active, you must wait until the 1st day of that month to initiate a downgrade for the following month.
#### Example [#example]
* **Requested more than 48 hours before the current package expires**: The downgrade takes effect after the current package expires.
* **Requested less than 48 hours before the current package expires**: Since a package is already active for the next month, you must wait until the new package starts to downgrade for the following month.
### Cancel subscription [#cancel-subscription]
To cancel a prepaid package, downgrade to the Free package. Downgrade rules apply.
## Top-up package [#top-up-package]
A top-up package is a pay-as-you-go plan. You can purchase a top-up package from the [`Agora Console`](https://console.agora.io/subscriptions/rtc-plans?tab=top-up) to cover usage that exceeds your monthly package quota.
### Purchase [#purchase]
You can purchase multiple top-up packages. They are deducted in the order of purchase.
#### Example: [#example-1]
* Purchased 250,000-minutes top-up packages on **August 15** and **August 16**. On **September 1**, minutes consumed in August are deducted from the first package, then from the second.
### Validity [#validity]
Each top-up package is valid for one year, starting from the purchase date to the end of the same month of the following year.
#### Example: [#example-2]
* Purchased on **August 15, 2025** → valid until **August 31, 2026**
Unused minutes expire after the validity period and cannot be carried forward.
### Cancel top-up package [#cancel-top-up-package]
Cancelling a top-up package is not supported.
## Free tier overage [#free-tier-overage]
For Free tier users, exceeding the included minutes and any purchased top-up minutes results in service suspension. Service resumes when you purchase additional top-up minutes or upgrade to a prepaid subscription package.
# IoT SDK overview (/en/realtime-media/iot/product-overview)
Agora's IoT SDK brings real-time audio and video engagement to smart devices with a lightweight design optimized for low-resource and battery-powered systems. Compatible with WebRTC and a wide range of chipsets, it supports seamless cross-platform development across mobile, PC, web, and smart displays. With robust performance in weak network conditions and integration with Agora’s real-time platform, it’s ideal for live streaming, remote control, and interactive IoT applications.
Use the IoT SDK to build lightweight, cross-platform apps for smart devices, from video doorbells to smart displays. Designed for low power consumption and exceptional network performance, it offers full compatibility with Agora’s RTC and signaling platforms, simplifying development for interactive and reliable IoT solutions.
Built on Agora’s Software-Defined Real-Time Network (SDRTN®), the IoT SDK ensures high connectivity, low latency, and maximum stability even in challenging network conditions. Add real-time audio and video capabilities to smart devices with a small footprint, minimal power consumption, and seamless interoperability with Agora’s real-time platform and RTC SDKs.
## Start building [#start-building]
## Product Features [#product-features]
IoT SDK integrates both real-time communication and signalling services, so you can easily develop features like live streaming and remote control at the same time.
Designed for low-resource embedded systems, IoT SDK has a small package footprint as low as 400kB so you don’t have to worry about system resource usage.
Based on low hardware requirements, IoT SDK works well with battery-powered devices like video doorbells.
IoT SDK is compatible with Video SDK for Web, and developers can build apps across platforms like mobile, PC, web, and smart displays like Amazon Alexa, without plugins, while supporting a wide range of chipsets and operating systems.
With end-to-end algorithmic network optimization, IoT SDK provides smooth live streaming even in weak network environments.
IoT SDK is interoperable with other Agora products so you can easily build up the companion apps on iOS, Android, or any other platform.
# SDK quickstart (/en/realtime-media/iot/quickstart)
<_PlatformTabsGroup groupMode="structured" canonicalPlatform="android" platforms="["android","linux-c"]" showTabs="true">
<_PlatformPanel platform="android">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="android" platform="android" />
The Internet of things (IoT) connects physical objects with sensors, processors, and software to enable data exchange with other devices. In related applications, such as apps for smart door bells and remote monitoring, IoT devices send or receive audio and video streams to perform their function. Agora IoT SDK gives you the ability to enable live voice and video streams on IoT devices on a variety of platforms and for many use cases, while also offering a compact SDK size, low memory usage, and low power consumption. Some typical application use-cases for IoT SDK include:
* **Real-time monitoring**: Integration into smart cameras and smart doorbells enables mobile device users to receive video feed from one or more cameras.
* **Two-way audio communication between mobile devices**: With IoT SDK integrated into smart watches, users perform two-way audio communication between mobile devices.
This page shows the minimum code you need to integrate audio and video communication features into your IoT devices using IoT SDK.
## Understand the tech [#understand-the-tech]
The lightweight IoT SDK is ideal for IoT applications due to its low power consumption. The SDK provides the following performance benefits:
* **Small SDK size**: The increase in app size is less than 400 KB after integration.
* **Low memory usage**: When the SDK is simultaneously sending and receiving 320x240 `H.264` video data, memory usage is less than 2 MB.
* **High connectivity**: Under normal conditions, connectivity is greater than 99%.
* **Data transfer speed**: A maximum of 50 Mbps for a user in a channel.
* **Network adaptability**: Non-aware recovery from up to 50% packet loss.
The following figure shows the workflow you need to integrate IoT SDK into your app:

## Prerequisites [#prerequisites]
In order to follow this procedure you must have:
To test the code used in this page you need to have:
* An Agora [account](build/manage-agora-account.md) and [project](build/manage-agora-account.md).
* A computer with Internet access.
Ensure that no firewall is blocking your network communication.
* Implemented the [SDK quickstart](index.md).
* IoT SDK for Android supports the following ABIs:
* armeabi-v7a
* arm64-v8a
* x86
* x86-64
## Project setup [#project-setup]
To integrate IoT SDK into your app, do the following:
1. In Android Studio, create a new **Phone and Tablet**, **Java** [Android project](https://developer.android.com/studio/projects/create-project) with an **Empty Activity**.
After creating the project, Android Studio automatically starts gradle sync. Ensure that the sync succeeds before you continue.
2. Add the IoT SDK to your Android project. To do this:
1. [Download](/en/api-reference/sdks?product=iot\&platform=android) the IoT SDK and extract the archive to a temporary folder ``.
2. Copy the following files and folders from `/agora_rtc_example_android/app/libs` to your project:
| File or folder | Path in your project |
| ------------------- | ------------------------ |
| Files: | |
| `agora-rtc-sdk.jar` | `/app/libs/` |
| Folders: | |
| `arm64-v8a` | `/app/src/main/jniLibs/` |
| `armeabi-v7a` | `/app/src/main/jniLibs/` |
| `x86` | `/app/src/main/jniLibs/` |
| `x86_64` | `/app/src/main/jniLibs/` |
3. In Android Studio, right-click the `/app/libs/agora-rtc-sdk.jar` file on the navigation bar and then select **Add as a library**.
3. Add permissions for network and device access.
In `/app/Manifests/AndroidManifest.xml`, add the following permissions after ``:
```java
```
4. Add sample audio and video files to the project resources folder:
1. In Android Studio, right-click the `res` folder and choose **New > Android Resource Directory**.
2. Under **Resource Type** select **raw** and click **OK**.
3. In the `/app/src/main/res/raw` folder, copy the files `send_audio_16k_1ch.pcm` and `send_video.h264` from the `/agora_rtc_example_android/app/src/main/res/raw` folder.
You are ready to add audio and video communication features to your IoT app.
## Implement audio and video communication [#implement-audio-and-video-communication]
This section shows how to use the IoT SDK to implement audio and video communication into your IoT app, step-by-step.
At startup, you initialize the engine. When a user taps a button, the app joins a channel and sends audio and video data to remote users.
### Implement the user interface [#implement-the-user-interface]
In this simple interface, you create two buttons to join and leave a channel. In `/app/res/layout/activity_main.xml`, replace the contents of the file with the following:
```xml
```
You see errors in your IDE. This is because the layout refers to methods that you create later.
### Handle the system logic [#handle-the-system-logic]
Import the necessary Android classes and handle Android permissions.
1. **Import the Android classes**
In `/app/java/com.example./MainActivity`, add the following lines after `package com.example.`:
```java
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.Manifest;
import android.content.pm.PackageManager;
import android.view.View;
import android.widget.Toast;
```
2. **Handle Android permissions**
Ensure that the permissions necessary to use audio and video features are granted. If the permissions are not granted, use the built-in Android feature to request them; if they are granted, return `true`.
In `/app/java/com.example./MainActivity`, add the following lines before the `onCreate` method:
```java
private static final int PERMISSION_REQ_ID = 22;
private static final String[] REQUESTED_PERMISSIONS =
{
Manifest.permission.RECORD_AUDIO,
Manifest.permission.CAMERA
};
private boolean checkSelfPermission()
{
if (ContextCompat.checkSelfPermission(this, REQUESTED_PERMISSIONS[0]) != PackageManager.PERMISSION_GRANTED ||
ContextCompat.checkSelfPermission(this, REQUESTED_PERMISSIONS[1]) != PackageManager.PERMISSION_GRANTED)
{
return false;
}
return true;
}
```
3. **Show status updates to your users**
In `/app/java/com.example./MainActivity`, add the following lines before the `onCreate` method:
```javascript
void showMessage(String message) {
runOnUiThread(() ->
Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show());
}
```
### Implement the channel logic [#implement-the-channel-logic]
To start Agora engine and join a channel, take the following steps:
1. **Import the IoT SDK classes**
In `/app/java/com.example./MainActivity`, add the following lines after the last `import` statement:
```java
import io.agora.rtc.AgoraRtcService;
import io.agora.rtc.AgoraRtcEvents;
```
2. **Declare the variables that you use to join a channel and stream Audio/Video**
In `/app/java/com.example./MainActivity`, add the following lines to the `MainActivity` class:
```java
private final String appId = ""; // The App ID of your project generated on Agora Console.
private String channelName = "";
private String token = ""; // A temp token generated on Agora Console.
private final int uid = 0; // An integer that identifies the local user.
private AgoraRtcService agoraEngine;
private final String deviceId = "MyDev01";
private boolean isJoined = false;
private final String rtcLicense = "";
private int connectionId;
private AudioSendThread audioThread = null;
private VideoSendThread videoThread = null;
```
3. **Setup Agora engine**
To use IoT features, you use the IoT SDK to create an `AgoraRtcService` instance. You then use this instance to open a connection. In `/app/java/com.example./MainActivity`, add the following code before the `onCreate` method:
```java
private void setupAgoraEngine(){
agoraEngine = new AgoraRtcService();
showMessage("RTC SDK version " + agoraEngine.getVersion());
// Initialize the engine
AgoraRtcService.RtcServiceOptions options = new AgoraRtcService.RtcServiceOptions();
options.areaCode = AgoraRtcService.AreaCode.AREA_CODE_GLOB;
options.productId = deviceId;
options.licenseValue = rtcLicense;
int ret = agoraEngine.init(appId, agoraRtcEvents, options);
if (ret != AgoraRtcService.ErrorCode.ERR_OKAY) {
showMessage("Fail to initialize the SDK " + ret);
agoraEngine = null;
return;
} else {
showMessage("Engine initialized");
}
// Create a connection
connectionId = agoraEngine.createConnection();
if (connectionId == AgoraRtcService.ConnectionIdSpecial.CONNECTION_ID_INVALID) {
showMessage("Failed to createConnection");
} else {
showMessage("Connected");
}
}
```
4. **At startup, ensure necessary permissions and initialize the engine**
In order to send video and audio streams, you need to ensure that the local user gives permission to access the camera and microphone at startup. After the permissions are granted, you setup the IoT SDK engine.
In `/app/java/com.example./MainActivity`, replace `onCreate` with the following code:
```java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// If all the permissions are granted, setup the engine
if (!checkSelfPermission()) {
ActivityCompat.requestPermissions(this, REQUESTED_PERMISSIONS, PERMISSION_REQ_ID);
}
setupAgoraEngine();
}
```
5. **Receive and handle engine events**
The `AgoraRtcService` provides a number of callbacks that enable you to respond to important events. To add an event handler to your app, in `/app/java/com.example./MainActivity`, add the following lines before `setupAgoraEngine`:
```java
private final AgoraRtcEvents agoraRtcEvents = new AgoraRtcEvents() {
@Override
public void onJoinChannelSuccess(int connId, int uid, int elapsed_ms) {
// Successfully joined a channel
isJoined = true;
showMessage("Successfully joined the channel: " + channelName);
// Create a thread to send video frames
videoThread = new VideoSendThread(getApplicationContext(), agoraEngine,
channelName, connectionId);
// Create a thread to send audio frames
audioThread = new AudioSendThread(getApplicationContext(), agoraEngine,
channelName, connectionId, 16000, 1, 2);
// Start audio and video threads
videoThread.sendStart();
audioThread.sendStart();
showMessage("Audio and video threads started");
}
@Override
public void onConnectionLost(int connId) {
// Connection was lost.
}
@Override
public void onRejoinChannelSuccess(int connId, int uid, int elapsed_ms) {
// The channel was successfully rejoined
}
@Override
public void onLicenseValidationFailure(int connId, int error) {
// When joining a channel, Agora will verify the License you passed in init().
// If verification fails, the SDK will trigger this callback
}
@Override
public void onError(int connId, int code, String msg) {
// A warning callback that reports a network or media related error.
// Normally, the app can ignore the warning message and the SDK will automatically recover.
}
@Override
public void onUserJoined(int connId, int uid, int elapsed_ms) {
// A remote user joined the channel.
}
@Override
public void onUserOffline(int connId, int uid, int reason) {
// A remote user went offline
}
@Override
public void onUserMuteAudio(int connId, int uid, boolean muted) {
// A remote user paused or resumed sending the audio stream
}
@Override
public void onUserMuteVideo(int connId, int uid, boolean muted) {
// A remote user paused or resumed sending the video stream
}
@Override
public void onKeyFrameGenReq(int connId, int requestedUid, int streamType) {
// Callback for key frame requests from remote users in the channel
}
@Override
public void onAudioData(int connId, int uid, int sent_ts, byte[] data, AgoraRtcService.AudioFrameInfo info) {
// Receive the audio frame callback of the remote user in the channel.
// Add code here to render audio.
}
@Override
public void onMixedAudioData(int connId, byte[] data, AgoraRtcService.AudioFrameInfo info) {
// Callback for receiving audio mixing data from local and remote users in the channel.
// This callback fires every 20 ms.
}
@Override
public void onVideoData(int connId, int uid, int sent_ts, byte[] data, AgoraRtcService.VideoFrameInfo info) {
// Receive video frame data from a remote user in the channel.
// Add code here to render video.
}
@Override
public void onTargetBitrateChanged(int connId, int targetBps) {
// Encoder code rate update notification callback.
// Use this callback to update the encoder bit rate.
}
@Override
public void onTokenPrivilegeWillExpire(int connId, String token) {
// The current authentication token will expire in 30 seconds
// Get a new token and call the renewToken method
}
@Override
public void onMediaCtrlReceive(int connId, int uid, byte[] payload) {
}
};
```
6. **Join a channel**
When a local user initiates a connection, call `joinChannel`. This method securely connects the local user to a channel using the authentication token. In `/app/java/com.example./MainActivity`, add the following code after `setupAgoraEngine`:
```java
public void joinChannel(View view) {
// Set channel options
AgoraRtcService.ChannelOptions channelOptions = new AgoraRtcService.ChannelOptions();
channelOptions.autoSubscribeAudio = true;
channelOptions.autoSubscribeVideo = true;
channelOptions.audioCodecOpt.audioCodecType
= AgoraRtcService.AudioCodecType.AUDIO_CODEC_TYPE_OPUS;
channelOptions.audioCodecOpt.pcmSampleRate = 16000;
channelOptions.audioCodecOpt.pcmChannelNum = 1;
// Join a channel
int ret = agoraEngine.joinChannel(connectionId, channelName,
uid, token, channelOptions);
if (ret != AgoraRtcService.ErrorCode.ERR_OKAY) {
showMessage("Failed to join a channel");
isJoined = false;
} else {
isJoined = true;
}
}
```
After joining an RTC channel, you can:
* Listen to the `onAudioData` callback to receive audio from users in the channel.
* Listen to the `onVideoData` callback to receive video from users in the channel.
* Call the `sendAudioData` method to send audio data.
* Call the `sendVideoData` method to send video data.
### Send audio and video data [#send-audio-and-video-data]
You send audio and video data in separate threads. The sending interval should be consistent with the video frame rate and audio frame length.
To stream audio and video data:
1. **Add a `StreamFile` class for managing media files**
You use `StreamFile` to open, read, and close audio and video files. In Android Studio, select **File > New > Java Class**. Name the class `StreamFile` and replace the contents of the file with the following code:
```java
package ; // Should be same as your MainActivity package
import android.content.Context;
import android.content.res.Resources;
import android.util.Log;
import java.io.IOException;
import java.io.InputStream;
public class StreamFile {
private final String TAG = "RTSADEMO/StreamFile";
private InputStream mInStream = null;
// Public Methods
public boolean open(Context ctx, int resId) {
try {
Resources resources = ctx.getResources();
mInStream = resources.openRawResource(resId);
} catch (Resources.NotFoundException notFoundExp) {
notFoundExp.printStackTrace();
Log.e(TAG, " file not found");
return false;
} catch (SecurityException fileSecExp) {
fileSecExp.printStackTrace();
Log.e(TAG, " security exception");
return false;
}
return true;
}
public void close() {
if (mInStream != null) {
try {
mInStream.close();
} catch (IOException fileCloseExp) {
fileCloseExp.printStackTrace();
}
mInStream = null;
}
}
public boolean isOpened() {
return (mInStream != null);
}
public int readData(byte[] readBuffer) {
if (mInStream == null) {
return -2;
}
try {
int readSize = mInStream.read(readBuffer);
return readSize;
} catch (IOException ioExp) {
return -3;
}
}
public int reset() {
if (mInStream == null) {
return -2;
}
try {
mInStream.reset();
return 0;
} catch (IOException ioExp) {
return -3;
}
}
}
```
2. **Add a thread class to send audio**
In Android Studio, select **File > New > Java Class**. Name the class `AudioSendThread` and replace the contents of the file with the following code:
```java
package ; // Should be same as your MainActivity package
import android.content.Context;
import android.util.Log;
import io.agora.rtc.AgoraRtcService;
import io.agora.rtc.AgoraRtcService.AudioFrameInfo;
import io.agora.rtc.AgoraRtcService.AudioDataType;
public class AudioSendThread extends Thread {
private final String TAG = "AudioSendThread";
private final Object mExitEvent = new Object();
private AgoraRtcService mRtcService;
private String mChannelName;
private int mConnectionId;
private volatile boolean mRunning = false;
private Context mContext;
private final int mSampleRate, mChannelNumber, mSampleBytes;
// Public Methods
public AudioSendThread(Context ctx, AgoraRtcService rtcService, String chnlName,
int connectionId, int sampleRate, int channelNumber, int sampleBytes) {
mContext = ctx;
mRtcService = rtcService;
mChannelName = chnlName;
mConnectionId = connectionId;
mSampleRate = sampleRate;
mChannelNumber = channelNumber;
mSampleBytes = sampleBytes;
}
public boolean sendStart() {
try {
mRunning = true;
this.start();
} catch (IllegalThreadStateException exp) {
exp.printStackTrace();
mRunning = false;
return false;
}
return true;
}
public void sendStop() {
mRunning = false;
synchronized (mExitEvent) {
try {
mExitEvent.wait(200);
} catch (InterruptedException interruptExp) {
interruptExp.printStackTrace();
}
}
}
@Override
public void run() {
Log.d(TAG, " ==>Enter");
StreamFile audioStream = new StreamFile();
audioStream.open(mContext, R.raw.send_audio_16k_1ch);
int bytesPerSec = mSampleRate*mChannelNumber*mSampleBytes;
int bufferSize = bytesPerSec / 50; // 20ms buffer
byte[] readBuffer = new byte[bufferSize];
byte[] sendBuffer = new byte[bufferSize];
while (mRunning && (audioStream.isOpened())) {
// Read an audio frame
int readSize = audioStream.readData(readBuffer);
if (readSize <= 0) {
Log.d(TAG, " read audio frame EOF, reset to start");
audioStream.reset();
continue;
}
// Copy data to send buffer
if (readSize != sendBuffer.length) {
sendBuffer = new byte[readSize];
}
System.arraycopy(readBuffer, 0, sendBuffer, 0, readSize);
// Send audio frame, data size is 20ms
AudioFrameInfo audioFrameInfo = new AudioFrameInfo();
audioFrameInfo.dataType = AudioDataType.AUDIO_DATA_TYPE_PCM;
int ret = mRtcService.sendAudioData(mConnectionId, sendBuffer, audioFrameInfo);
if (ret < 0) {
Log.e(TAG, " sendAudioData() failure, ret=" + ret);
}
sleepCurrThread(20); // sleep 20ms
}
audioStream.close();
Log.d(TAG, " <==Exit");
// Notify: exit audio thread
synchronized(mExitEvent) {
mExitEvent.notify();
}
}
boolean sleepCurrThread(long milliseconds) {
try {
Thread.sleep(milliseconds);
} catch (InterruptedException inerruptExp) {
inerruptExp.printStackTrace();
return false;
}
return true;
}
}
```
3. **Add a thread class to send video**
In Android Studio, select **File > New > Java Class**. Name the class `VideoSendThread` and replace the contents of the file with the following code:
```java
package ; // Should be same as your MainActivity package
import android.content.Context;
import android.util.Log;
import io.agora.rtc.AgoraRtcService;
import io.agora.rtc.AgoraRtcService.VideoFrameInfo;
import io.agora.rtc.AgoraRtcService.VideoDataType;
import io.agora.rtc.AgoraRtcService.VideoFrameType;
import io.agora.rtc.AgoraRtcService.VideoFrameRate;
public class VideoSendThread extends Thread {
private final String TAG = "VideoSendThread";
private final static int[] FRAME_SIZE_ARR = {
9654, 1617, 1884, 2003, 2362, 1887, 1773, 1943, 2081, 2000, 1975, 2093, 2247, 2469, 2578,
2554, 2548, 2116, 2327, 2254, 2270, 1565, 2498, 2409, 2783, 2394, 2248, 1337, 1318, 1186,
12217, 1366, 1570, 1970, 2066, 2091, 1856, 2477, 1941, 1956, 1329, 1944, 2054, 1706, 1714,
1607, 1757, 2381, 2240, 2555, 2224, 1929, 1622, 1785, 2320, 2511, 1961, 2051, 2340, 1958,
12223, 1605, 1690, 1950, 1848, 2130, 2177, 2539, 1868, 2043, 1942, 2188, 1974, 2272, 1716,
2150, 1837, 2386, 2720, 2282, 2561, 2237, 1848, 1895, 2511, 2366, 2228, 1966, 1829, 2097,
11302, 2034, 2552, 2679, 3223, 2408, 1921, 1721, 1899, 1630, 1689, 1602, 1798, 1456, 1914,
1625, 1586, 1002, 1538, 1637, 1582, 1386, 1752, 1527, 1739, 1448, 1641, 1279, 1501, 1523,
11903, 1057, 1504, 1495, 1917, 2051, 2237, 2169, 2437, 2315, 2162, 1870, 1962, 2034, 2141,
1676, 1874, 2068, 2468, 2429, 2458, 2583, 2626, 1967, 2558, 2301, 2473, 2138, 2152, 1712,
10497, 1528, 2165, 2941, 3253, 2867, 3679, 3621, 3564, 1723, 2013, 1921, 1757, 1517, 1899,
1407, 1480, 1403, 1604, 1836, 2442, 2680, 3154, 3329, 3219, 2612, 2759, 2783, 2622, 2855,
10619, 2145, 2259, 2513, 2779, 2757, 3199, 3081, 2684, 2977, 2884, 3170, 3346, 3164, 3102,
3486, 3190, 2414, 2614, 2425, 2705, 3173, 3114, 2769, 2650, 2604, 2355, 2283, 2251, 2288,
11091, 2930, 3032, 2907, 2853, 3308, 2904, 3742, 3324, 4308, 4067, 2709, 2927, 1909, 2109,
2210, 2875, 2119, 2772, 4059, 4111, 2840, 2528, 1920, 3217, 1615, 2640, 2209, 3503, 2085,
19570, 1705, 2747, 2420, 2553, 2435, 3508, 2084, 2188, 1994, 5324, 1771, 1397, 2608, 3201,
2728, 2675, 3498, 1783, 1308, 2611, 2799, 3630, 2243, 1898, 2052, 3272, 2271, 2976, 3679,
22520, 712, 1650, 1846, 2142, 1464, 1903, 1828, 2564, 1365, 1359, 1262, 3015, 2596, 2472,
2639, 3447, 2879, 2546, 2643, 3240, 1759, 2123, 1277, 2336, 1664, 2104, 1881, 1267, 516,
};
private final static int FRAME_COUNT = 300;
private final Object mExitEvent = new Object();
private Context mContext;
private AgoraRtcService mRtcService;
private String mChannelName;
private int mConnectionId;
private volatile boolean mRunning = false;
public VideoSendThread(Context ctx, AgoraRtcService rtcService, String chnlName, int connectionId) {
mContext = ctx;
mRtcService = rtcService;
mChannelName = chnlName;
mConnectionId = connectionId;
}
public boolean sendStart() {
try {
mRunning = true;
this.start();
} catch (IllegalThreadStateException exp) {
exp.printStackTrace();
mRunning = false;
return false;
}
return true;
}
public void sendStop() {
mRunning = false;
synchronized (mExitEvent) {
try {
mExitEvent.wait(200);
} catch (InterruptedException interruptExp) {
interruptExp.printStackTrace();
}
}
}
@Override
public void run() {
Log.d(TAG, " ==>Enter");
StreamFile videoStream = new StreamFile();
videoStream.open(mContext, R.raw.send_video);
int frameIndex = 0;
while (mRunning && (videoStream.isOpened())) {
// Read a video frame
if (frameIndex >= FRAME_COUNT) {
Log.d(TAG, " read video frame EOF, reset to start");
frameIndex = 0;
videoStream.reset();
}
int frameSize = FRAME_SIZE_ARR[frameIndex];
byte[] videoBuffer = new byte[frameSize];
int readSize = videoStream.readData(videoBuffer);
if (readSize <= 0) {
Log.e(TAG, " read video frame error, readSize=" + readSize);
}
// Send a video frame
int streamId = 0;
VideoFrameInfo videoFrameInfo = new VideoFrameInfo();
videoFrameInfo.dataType = VideoDataType.VIDEO_DATA_TYPE_H264;
videoFrameInfo.frameType = VideoFrameType.VIDEO_FRAME_KEY;
videoFrameInfo.frameRate = VideoFrameRate.VIDEO_FRAME_RATE_FPS_15;
int ret = mRtcService.sendVideoData(mConnectionId, videoBuffer, videoFrameInfo);
if (ret < 0) {
Log.e(TAG, " sendVideoData() failure, ret=" + ret
+ ", dataSize=" + videoBuffer.length);
}
frameIndex++;
videoBuffer = null;
sleepCurrThread(66);
}
videoStream.close();
Log.d(TAG, " <==Exit");
// Notify: exit video thread
synchronized(mExitEvent) {
mExitEvent.notify();
}
}
boolean sleepCurrThread(long milliseconds) {
try {
Thread.sleep(milliseconds);
} catch (InterruptedException inerruptExp) {
inerruptExp.printStackTrace();
return false;
}
return true;
}
}
```
### Stop your app [#stop-your-app]
In this implementation, you initiate and destroy the engine when the app opens and closes. The local user joins and leaves a channel using the same engine instance. To elegantly exit your app:
1. **Leave the channel when a user ends the call**
When a user presses the **Leave** button, use `leaveChannel` to exit the channel. In `/app/java/com.example./MainActivity`, add `leaveChannel` after `joinChannel`:
```java
public void leaveChannel(View view) {
if (!isJoined) {
showMessage("Join a channel first");
} else {
// Stop audio and threads
if (videoThread != null) {
videoThread.sendStop();
videoThread = null;
}
if (audioThread != null) {
audioThread.sendStop();
audioThread = null;
}
showMessage("Audio and video threads stopped");
// Leave the channel
int ret = agoraEngine.leaveChannel(connectionId);
if (ret != AgoraRtcService.ErrorCode.ERR_OKAY) {
showMessage("Failed to leave the channel");
} else {
isJoined = false;
showMessage("Left the channel " + channelName);
}
}
}
```
2. **Release the resources used by your app**
When a user closes the app, use `onDestroy` to release all the resources in use. In `/app/java/com.example./MainActivity`, add `onDestroy` after `onCreate`:
```java
protected void onDestroy() {
super.onDestroy();
// Destroy the connection
agoraEngine.destroyConnection(connectionId);
// Release all resources allocated to the SDK by the init() method
agoraEngine.fini();
// Invalidate the connection ID
connectionId = AgoraRtcService.ConnectionIdSpecial.CONNECTION_ID_INVALID;
}
```
## Test your implementation [#test-your-implementation]
To test your implementation, take the following steps:
1. [Generate a temporary token](build/manage-agora-account.md) in Agora Console.
2. In your browser, navigate to the [Agora web demo](https://webdemo.agora.io/basicVideoCall/index.html) and update *App ID*, *Channel*, and *Token* with the values for your temporary token, then click **Join**.
3. In Android Studio, in `app/java/com.example./MainActivity`, update `appId`, `channelName`, and `token` with the values for your temporary token.
4. Connect a physical Android device to your development device.
5. In Android Studio, click **Run app**. A moment later you see the project installed on your device.
If this is the first time you run the project, you need to grant microphone and camera access to your app.
6. Click **Join** to join a channel.
You hear audio and see a video playing in the web demo app, pushed from the IoT SDK demo project.
7. Click **Leave**.
Audio and video streaming stops and you exit the channel.
## Reference [#reference]
This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.
### API reference [#api-reference]
* [AgoraRtcService](https://api-ref.agora.io/en/iot-sdk/android/1.x/classio_1_1agora_1_1rtc_1_1_agora_rtc_service.html)
* [AgoraRtcEvents](https://api-ref.agora.io/en/iot-sdk/android/1.x/interfaceio_1_1agora_1_1rtc_1_1_agora_rtc_events.html)
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="linux-c">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="android" platform="linux-c" />
The Internet of things (IoT) connects physical objects with sensors, processors, and software to enable data exchange with other devices. In related applications, such as apps for smart door bells and remote monitoring, IoT devices send or receive audio and video streams to perform their function. Agora IoT SDK gives you the ability to enable live voice and video streams on IoT devices on a variety of platforms and for many use cases, while also offering a compact SDK size, low memory usage, and low power consumption. Some typical application use-cases for IoT SDK include:
* **Real-time monitoring**: Integration into smart cameras and smart doorbells enables mobile device users to receive video feed from one or more cameras.
* **Two-way audio communication between mobile devices**: With IoT SDK integrated into smart watches, users perform two-way audio communication between mobile devices.
This page shows the minimum code you need to integrate audio and video communication features into your IoT devices using IoT SDK.
## Understand the tech [#understand-the-tech-1]
The lightweight IoT SDK is ideal for IoT applications due to its low power consumption. The SDK provides the following performance benefits:
* **Small SDK size**: The increase in app size is less than 400 KB after integration.
* **Low memory usage**: When the SDK is simultaneously sending and receiving 320x240 `H.264` video data, memory usage is less than 2 MB.
* **High connectivity**: Under normal conditions, connectivity is greater than 99%.
* **Data transfer speed**: A maximum of 50 Mbps for a user in a channel.
* **Network adaptability**: Non-aware recovery from up to 50% packet loss.
The following figure shows the workflow you need to integrate IoT SDK into your app:

## Prerequisites [#prerequisites-1]
In order to follow this procedure you must have:
To test the code used in this page you need to have:
* An Agora [account](build/manage-agora-account.md) and [project](build/manage-agora-account.md).
* A computer with Internet access.
Ensure that no firewall is blocking your network communication.
* Implemented the [SDK quickstart](index.md).
* IoT SDK supports the following platforms:
* x86 / x86\_64: gcc 2.6.35 or higher
* ARM:
* arm-linux-gnueabi 4.4.x or higher
* arm-linux-gnueabihf 4.8.x or higher
* aarch64-linux-gnu 4.8.x or higher
* arm-linux-musleabihf 5.2.x or higher
* arm-linux-musleabi 5.5.x or higher
* aarch64-linux-musl 5.5.x or higher
* arm-linux-uclibceabi 4.4.x or higher
* arm-linux-uclibceabihf 4.8.x or higher
* MIPS: mips-linux-uclibceabihf 4.7.x or higher
## Project setup [#project-setup-1]
Take the following steps to set up the IoT SDK sample project:
1. Install the `build-essential` package.
If you don't have development tools installed on your machine, execute the following command in the terminal to install the `build-essential` package which contains `libc`, `gcc`, `g++`, and other development tools.
```bash
sudo apt update && sudo apt install build-essential
```
2. Ensure `cmake` v3.6.0 or above is installed.
The sample project uses `cmake` to control the compilation process. Execute `cmake --version` to check that the version number is `3.6.0` or above. If the version number is lower, remove `cmake` by executing the command:
```bash
sudo apt remove cmake
```
To install the latest version of `cmake`, execute the following command:
```bash
sudo apt install cmake
```
3. [Download](/en/api-reference/sdks?platform=linux) the appropriate SDK package.
Agora provides several SDK packages configured for various architectures, operating systems, C libraries, and compilation options. The SDK package name indicates the intended platform and compilation options. For example, the name `armv7a-linux-uclibceabi` means that the SDK package is configured for the `armv7a` architecture and the Linux operating system. The compilation tool chain uses `uClibc`, and the compilation option is soft floating point.
For the purpose of this document, we use the `x86_64-linux-gnu` SDK package. You may choose the package that best matches your development environment and make appropriate modifications where required.
4. Extract the package
Extract the downloaded SDK package into `` on your development machine.
## Implement audio and video communication [#implement-audio-and-video-communication-1]
This section presents code snippets from the sample project that you can reuse in your own project to integrate IoT SDK.
Open the file `/agora_rtsa_sdk/example/hello_rtsa/hello_rtsa.c` in a code editor and examine the code. Reuse the following code blocks from this project to implement the desired functionality in your own project:
1. **Initialize the SDK, verify license, and create a connection**
The following code initializes Agora engine event handler, configures options, and creates a connection that you use to send and receive data:
```c
// Initialize the event handler
agora_rtc_event_handler_t event_handler = { 0 };
app_init_event_handler(&event_handler, config);
// Set Agora engine options
rtc_service_option_t service_opt = { 0 };
service_opt.area_code = config->area;
service_opt.log_cfg.log_path = config->p_sdk_log_dir;
service_opt.log_cfg.log_level = RTC_LOG_DEBUG;
snprintf(service_opt.license_value, sizeof(service_opt.license_value), "%s", config->p_license);
// Initialize Agora rtc sdk
rval = agora_rtc_init(p_appid, &event_handler, &service_opt);
if (rval < 0) {
LOGE("Failed to initialize Agora sdk, reason: %s", agora_rtc_err_2_str(rval));
return -1;
}
// Set bandwidth parameters
rval = agora_rtc_set_bwe_param(CONNECTION_ID_ALL,
DEFAULT_BANDWIDTH_ESTIMATE_MIN_BITRATE,
DEFAULT_BANDWIDTH_ESTIMATE_MAX_BITRATE,
DEFAULT_BANDWIDTH_ESTIMATE_START_BITRATE);
if (rval != 0) {
LOGE("Failed set bwe param, reason: %s", agora_rtc_err_2_str(rval));
return -1;
}
// Create a connection
rval = agora_rtc_create_connection(&g_app.conn_id);
if (rval < 0) {
LOGE("Failed to create connection, reason: %s", agora_rtc_err_2_str(rval));
return -1;
}
```
2. **Handle Agora engine events**
IoT SDK notifies you of important events, such as users joining or leaving a channel and receipt of audio or video data, through callbacks in the event handler. The following code adds methods to handle IoT SDK events and initializes the event handler.
```c
static void __on_join_channel_success(connection_id_t conn_id, uint32_t uid, int elapsed)
{
g_app.b_connected_flag = true;
connection_info_t conn_info = { 0 };
agora_rtc_get_connection_info(g_app.conn_id, &conn_info);
LOGI("[conn-%u] Join the channel %s successfully, uid %u elapsed %d ms", conn_id,
conn_info.channel_name, uid, elapsed);
}
static void __on_connection_lost(connection_id_t conn_id)
{
g_app.b_connected_flag = false;
LOGW("[conn-%u] Lost connection from the channel", conn_id);
}
static void __on_rejoin_channel_success(connection_id_t conn_id, uint32_t uid, int elapsed_ms)
{
g_app.b_connected_flag = true;
LOGI("[conn-%u] Rejoin the channel successfully, uid %u elapsed %d ms", conn_id, uid, elapsed_ms);
}
static void __on_user_joined(connection_id_t conn_id, uint32_t uid, int elapsed_ms)
{
LOGI("[conn-%u] Remote user \"%u\" has joined the channel, elapsed %d ms", conn_id, uid, elapsed_ms);
}
static void __on_user_offline(connection_id_t conn_id, uint32_t uid, int reason)
{
LOGI("[conn-%u] Remote user \"%u\" has left the channel, reason %d", conn_id, uid, reason);
}
static void __on_user_mute_audio(connection_id_t conn_id, uint32_t uid, bool muted)
{
LOGI("[conn-%u] audio: uid=%u muted=%d", conn_id, uid, muted);
}
static void __on_user_mute_video(connection_id_t conn_id, uint32_t uid, bool muted)
{
LOGI("[conn-%u] video: uid=%u muted=%d", conn_id, uid, muted);
}
static void __on_error(connection_id_t conn_id, int code, const char *msg)
{
if (code == ERR_SEND_VIDEO_OVER_BANDWIDTH_LIMIT) {
LOGE("Not enough uplink bandwidth. Error msg \"%s\"", msg);
return;
}
if (code == ERR_INVALID_APP_ID) {
LOGE("Invalid App ID. Please double check. Error msg \"%s\"", msg);
} else if (code == ERR_INVALID_CHANNEL_NAME) {
LOGE("Invalid channel name for conn_id %u. Please double check. Error msg \"%s\"", conn_id,
msg);
} else if (code == ERR_INVALID_TOKEN || code == ERR_TOKEN_EXPIRED) {
LOGE("Invalid token. Please double check. Error msg \"%s\"", msg);
} else if (code == ERR_DYNAMIC_TOKEN_BUT_USE_STATIC_KEY) {
LOGE("Dynamic token is enabled but is not provided. Error msg \"%s\"", msg);
} else {
LOGW("Error %d is captured. Error msg \"%s\"", code, msg);
}
g_app.b_stop_flag = true;
}
static void __on_license_failed(connection_id_t conn_id, int reason)
{
LOGE("License verified failed, reason: %d", reason);
g_app.b_stop_flag = true;
}
static void __on_audio_data(connection_id_t conn_id, const uint32_t uid, uint16_t sent_ts, const void *data, size_t len, const audio_frame_info_t *info_ptr)
{
LOGD("[conn-%u] on_audio_data, uid %u sent_ts %u data_type %d, len %zu", conn_id, uid, sent_ts,
info_ptr->data_type, len);
write_file(g_app.audio_file_writer, info_ptr->data_type, data, len);
}
static void __on_mixed_audio_data(connection_id_t conn_id, const void *data, size_t len,
const audio_frame_info_t *info_ptr)
{
LOGD("[conn-%u] on_mixed_audio_data, data_type %d, len %zu", conn_id, info_ptr->data_type, len);
write_file(g_app.audio_file_writer, info_ptr->data_type, data, len);
}
static void __on_video_data(connection_id_t conn_id, const uint32_t uid, uint16_t sent_ts, const void *data, size_t len, const video_frame_info_t *info_ptr)
{
LOGD("[conn-%u] on_video_data: uid %u sent_ts %u data_type %d frame_type %d stream_type %d len %zu",
conn_id, uid, sent_ts, info_ptr->data_type, info_ptr->frame_type, info_ptr->stream_type,
len);
write_file(g_app.video_file_writer, info_ptr->data_type, data, len);
}
static void __on_target_bitrate_changed(connection_id_t conn_id, uint32_t target_bps)
{
LOGI("[conn-%u] Bandwidth change detected. Please adjust encoder bitrate to %u kbps", conn_id, target_bps / 1000);
}
static void __on_key_frame_gen_req(connection_id_t conn_id, uint32_t uid,
video_stream_type_e stream_type)
{
LOGW("[conn-%u] Frame loss detected. Please notify the encoder to generate key frame immediately",
conn_id);
}
// Initialize the event_handler
static void app_init_event_handler(agora_rtc_event_handler_t *event_handler, app_config_t *config)
{
event_handler->on_join_channel_success = __on_join_channel_success;
event_handler->on_connection_lost = __on_connection_lost;
event_handler->on_rejoin_channel_success = __on_rejoin_channel_success;
event_handler->on_user_joined = __on_user_joined;
event_handler->on_user_offline = __on_user_offline;
event_handler->on_user_mute_audio = __on_user_mute_audio;
event_handler->on_user_mute_video = __on_user_mute_video;
event_handler->on_target_bitrate_changed = __on_target_bitrate_changed;
event_handler->on_key_frame_gen_req = __on_key_frame_gen_req;
event_handler->on_video_data = __on_video_data;
event_handler->on_error = __on_error;
event_handler->on_license_validation_failure = __on_license_failed;
if (config->enable_audio_mixer) {
event_handler->on_mixed_audio_data = __on_mixed_audio_data;
} else {
event_handler->on_audio_data = __on_audio_data;
}
}
```
3. **Join a channel**
To join a channel, you configure `channel_options` and pass the object to `agora_rtc_join_channel` with the connection ID, channel name, user ID, and authentication token.
```c
// Set Channel options
rtc_channel_options_t channel_options;
memset(&channel_options, 0, sizeof(channel_options));
channel_options.auto_subscribe_audio = true;
channel_options.auto_subscribe_video = true;
channel_options.enable_audio_mixer = config->enable_audio_mixer;
// Configure audio
if (config->audio_data_type == AUDIO_DATA_TYPE_PCM) {
/* To send PCM data instead of encoded audio like AAC or Opus, enable audio codec, as well as configure the PCM sample rate and number of channels here*/
channel_options.audio_codec_opt.audio_codec_type = config->audio_codec_type;
channel_options.audio_codec_opt.pcm_sample_rate = config->pcm_sample_rate;
channel_options.audio_codec_opt.pcm_channel_num = config->pcm_channel_num;
}
// Join a channel
rval = agora_rtc_join_channel(g_app.conn_id, config->p_channel, config->uid, p_token,
&channel_options);
if (rval < 0) {
LOGE("Failed to join channel \"%s\", reason: %s", config->p_channel, agora_rtc_err_2_str(rval));
return -1;
}
```
4. **Stream audio and video data**
You send audio and video data in a timed loop. The sending interval must be consistent with the video frame rate and audio frame length.
The sample app uses the following code loop to send audio and video data:
```c
// Set audio and video send intervals
int audio_send_interval_ms = DEFAULT_SEND_AUDIO_FRAME_PERIOD_MS;
int video_send_interval_ms = 1000 / config->send_video_frame_rate;
void *pacer = pacer_create(audio_send_interval_ms, video_send_interval_ms);
while (!g_app.b_stop_flag) {
// Skip sending data if receive_data_only flag is on
if (config->receive_data_only) {
util_sleep_ms(1000);
continue;
}
// Send an audio frame
if (g_app.b_connected_flag && is_time_to_send_audio(pacer)) {
app_send_audio();
}
// Send a video frame
if (g_app.b_connected_flag && is_time_to_send_video(pacer)) {
app_send_video();
}
// Sleep and wait until it is time to send the next frame
wait_before_next_send(pacer);
}
```
5. **Send an audio frame**
To send audio, you read data from an audio file and call `agora_rtc_send_audio_data`. The following code sends an audio frame each time it is called:
```c
static int app_send_audio(void)
{
app_config_t *config = &g_app.config;
// Get audio frame data
frame_t frame;
if (file_parser_obtain_frame(g_app.audio_file_parser, &frame) < 0) {
LOGE("The file parser failed to obtain audio frame");
return -1;
}
// Send an audio frame
audio_frame_info_t info = { 0 };
info.data_type = config->audio_data_type;
int rval = agora_rtc_send_audio_data(g_app.conn_id, frame.ptr, frame.len, &info);
if (rval < 0) {
LOGE("Failed to send audio data, reason: %s", agora_rtc_err_2_str(rval));
file_parser_release_frame(g_app.audio_file_parser, &frame);
return -1;
}
file_parser_release_frame(g_app.audio_file_parser, &frame);
return 0;
}
```
6. **Send a video frame**
To send video, you read frame data from a video file, create a `video_frame_info_t`, and call `agora_rtc_send_video_data`. The following code sends a video frame each time it is called:
```c
static int app_send_video(void)
{
app_config_t *config = &g_app.config;
uint8_t stream_id = 0;
// Get video frame data
frame_t frame;
if (file_parser_obtain_frame(g_app.video_file_parser, &frame) < 0) {
LOGE("The file parser failed to obtain video frame");
return -1;
}
video_frame_info_t info = { 0 };
// Specify if this is a key frame or a delta frame
info.frame_type = frame.u.video.is_key_frame ? VIDEO_FRAME_KEY : VIDEO_FRAME_DELTA;
info.frame_rate = config->send_video_frame_rate;
info.data_type = config->video_data_type;
info.stream_type = VIDEO_STREAM_HIGH;
// Send a video frame
int rval = agora_rtc_send_video_data(g_app.conn_id, frame.ptr, frame.len, &info);
if (rval < 0) {
LOGE("Failed to send video data, reason: %s", agora_rtc_err_2_str(rval));
file_parser_release_frame(g_app.video_file_parser, &frame);
return -1;
}
file_parser_release_frame(g_app.video_file_parser, &frame);
return 0;
}
```
7. **Leave a channel**
To leave a channel you call `agora_rtc_leave_channel` with the connection ID.
```c
agora_rtc_leave_channel(g_app.conn_id);
```
8. **Stop the app**
When a user exits the app, you destroy the connection and call `fini()` to release resources. The following code elegantly stops the app:
```c
// Destroy connection
agora_rtc_destroy_connection(g_app.conn_id);
// Finalize rtc sdk
agora_rtc_fini();
```
## Test your implementation [#test-your-implementation-1]
To test your implementation, take the following steps:
1. [Generate a temporary token](build/manage-agora-account.md#generate-a-temporary-token) in Agora Console.
2. In your browser, navigate to the [Agora web demo](https://webdemo.agora.io/basicVideoCall/index.html) and update *App ID*, *Channel*, and *Token* with the values for your temporary token, then click **Join**.
3. Replace the App ID, channel ID, and token with values from Agora Console.
4. Compile the sample project
In the terminal, execute the following commands:
```bash
cd /agora_rtsa_sdk/example
./build-x86_64.sh
```
5. Launch the sample app:
To do this:
1. In the terminal, navigate to the output folder:
```bash
cd out/x86_64
```
2. Launch the compiled app
```bash
./hello_rtsa --app-id --channel-id --token
```
You hear an audio file being played and see a video file in the web demo app. These files are
pushed from your app.
## Reference [#reference-1]
This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.
### API reference [#api-reference-1]
* [Overview](https://api-ref.agora.io/en/iot-sdk/linux/1.x/index.html)
* [Events](https://api-ref.agora.io/en/iot-sdk/linux/1.x/structagora__rtc__event__handler__t.html)
<_PlatformProcessedMarker close="true" />
# Core concepts (/en/realtime-media/marketplace/core-concepts)
RTC (Real-Time Communication) refers to real-time communication technology, that allows almost instant exchange of audio, video, and other data between the sender and the receiver.
Agora SDKs provide real-time audio and video interaction services, with multi-platform and multi-device support. This includes high-definition video calls, voice-only calls, interactive live streaming, as well as one-on-one and multi-group chats.
This article introduces the key processes and concepts you need to know to use Agora SDKs.
## Using the Agora Console [#using-the-agora-console]
To use Agora SDKs, create an audio and video project in the Agora Console first. See [Agora account management](manage-agora-account.mdx) for details.

#### Agora Console [#agora-console]
[Agora Console](https://console.agora.io) is the main dashboard where you manage your Agora projects and services. Agora Console provides an intuitive interface for developers to query and manage their Agora account. After registering an [Agora Account](https://console.agora.io), you use the Agora Console to perform the following tasks:
* Manage the account
* Create and configure Agora projects and services
* Get an App ID
* Manage members and roles
* Check call quality and usage
* Check bills and make payments
* Access product resources
Agora also provides RESTful APIs that you use to implement features such as creating a project and fetching usage numbers programmatically.
## General concepts [#general-concepts]
Agora uses the following basic concepts:
### App ID [#app-id]
The App ID is a random string generated within [Agora Console](https://console.agora.io) when you create a new project. You can create multiple projects in your account; each project has a different App ID. This App ID enables your app users to communicate securely with each other. When you initialize the Agora engine in your app, you pass the App ID as an argument. The App ID is also used to create the authentication tokens that ensure secure communication in a channel. You [retrieve your App ID](https://console.agora.io/project-management) using Agora Console.
Agora uses this App ID to identify each app, provide billing and other statistical data services.
### App certificate [#app-certificate]
An App certificate is a string generated by Agora Console to enable token authentication. It is required for generating a Video SDK or Signaling authentication token.
To use your App certificate for setting up a token server, see [Deploy a token server](../video/build/deploy-token-server.mdx).
### Token [#token]
A token is a dynamic key that is used by the Agora authentication server to check user permissions. You use Agora Console to generate a temporary token for testing purposes during the development process. In a production environment, you implement a token server in your security infrastructure to control access to your channels.
After obtaining the App ID, App Certificate, and Token in the Agora Console, you can start implementing basic audio and video communication in your app.
### Channel [#channel]
Agora uses the *channel name* to identify a channel. Users who specify the same *channel name* join a common channel and interact with each other. A channel is created when the first user joins. It ceases to exist when the last user leaves.
You create a channel by calling the methods for transmitting real-time data. Agora uses different channels to transmit different types of data. The Video SDK channel transmits audio or video data, while the Signaling channel transmits messaging or signaling data. The Video SDK and Signaling channels are independent of each other.
Additional components provided by Agora, such as On-Premise Recording and Cloud Recording, join the Video SDK channel and provide real-time recording, transmission acceleration, media playback, and content moderation.
### Channel profile [#channel-profile]
The SDK applies different optimization methods according to the selected channel profile. Agora supports the following channel profiles:
| Channel profile | Description |
| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `COMMUNICATION` | This profile is suitable for one-on-one or group calls, where all users in the channel talk freely. |
| `LIVE_BROADCASTING` | In a live streaming channel, users have two client roles: *host* and *audience*. The *host* sends and receives audio or video, while the *audience* only receives audio or video with the sending function disabled. |
### Stream [#stream]
A stream is a sequence of digitally-encoded coherent signals that contains audio or video data. Users in a channel [publish](#publish) local streams and [subscribe](#subscribe) to remote streams from other users.
### Publish [#publish]
Publishing is the act of sending a user's audio or video data to the channel. Usually, the published stream is created by the audio data sampled from a microphone or the video data captured by a camera. You can also publish media streams from other sources, such as an online music file or the user's screen.
After successfully publishing a stream, the SDK continues sending media data to other users in the channel. By publishing the local stream and subscribing to remote streams, users communicate with each other in real-time.
### Subscribe [#subscribe]
Subscribing is the act of receiving media streams published by remote users to the channel. A user receives audio and video data from other users by subscribing to one or more of their streams. You either directly play the subscribed streams or process incoming data for other purposes such as recording or capturing screenshots.
### User ID [#user-id]
A User ID (UID) identifies a user in a channel. Each user in a channel should have a unique user ID. If you do not specify a user ID when the user joins a channel, a UID is automatically generated and assigned to the user.
### User role [#user-role]
A user role is used to define whether users in the channel have permission to publish streams. There are two user roles:
* Host: A user who can publish streams in a channel.
* Audience: A user who cannot publish streams in a channel. Users with this role can only subscribe to remote audio and video streams.
### Connection (RtcConnection) [#connection-rtcconnection]
The connection between the SDK and the channel. When you need to publish or receive multiple streams in multiple channels, a connection is used to specify the target channel.
### Agora SDRTN® [#agora-sdrtn]
Agora's core engagement services are powered by its Software Defined Real-Time Network (SD-RTN™) that is accessible and available anytime, anywhere around the world. The software-defined network isn't confined by device, phone numbers, or a telecommunication provider's coverage area like traditional networks. Agora SDRTN® has data centers globally that cover over 200+ countries and regions. The network delivers sub-second latency and high availability of real-time video and audio anywhere on the globe. With Agora SDRTN®, Agora can deliver live user engagement experiences in the form of real-time communication (RTC) with the following advantages:
* Unmatched quality of service
* High availability and accessibility
* True scalability
* Low Cost
# Extensions Marketplace overview (/en/realtime-media/marketplace)
Agora's Extensions Marketplace makes it simple to extend the functionality of live voice and video streaming SDKs. With first-party extensions and third-party solutions from industry-leading partners, you can add advanced capabilities like noise cancelation, face filters, and voice effects with just a few lines of code.
Extensions Marketplace streamlines feature discovery, activation, and integration with unified billing, allowing you to enhance your applications without added complexity. Whether you're building for social, education, or enterprise use-cases, Extensions Marketplace empowers you to create richer, more interactive experiences.
## Product features [#product-features]
Browse the Extensions Gallery to find specific functionality or get inspired by new extensions.
Activate and integrate new functionality with a click and a few lines of code.
Get to market faster by instantly adding advanced features to your app without having to build them.
Pricing is straightforward and you can pay for all of your extensions in one unified interface.
Manage the activation status of your extensions from one place in the Agora Console.
Add as many extensions as you want to customize and extend the functionality of your experience.
## Start building [#start-building]
# Agora account management (/en/realtime-media/marketplace/manage-agora-account)
This page shows you how to sign up for an Agora account, create a new project, and get the app ID and app certificate to generate a temporary token.
## Get started with Agora [#get-started-with-agora]
To join an app session, you need an Agora App ID. This section shows you how to set up an Agora account, create an Agora project and get the required information from [Agora Console](https://console.agora.io).
### Sign up for an Agora account [#sign-up-for-an-agora-account]
To use Agora products and services, create an Agora account with your email, phone number, or a third-party account.
Sign up with email
Sign up with a third-party account
1. Go to the [signup page](https://sso.agora.io/en/signup).
2. Fill in the required fields.
3. Carefully read the **Terms of Service**, **Privacy Policy**, and **Acceptable Use Policy**, and tick the checkbox.
4. Click **Continue**.
5. Enter your **verification code** and click **Confirm**.
6. Follow the on-screen instructions to provide your name, company name, and phone number, set a password, and click **Continue**.
1. On the [Agora Console](https://console.agora.io) login page, select the third-party account you want to use.
2. Follow the on-screen instructions to complete verification.
3. Click **Create a new account**.
4. Carefully read the **Terms of Service**, **Privacy Policy**, and **Acceptable Use Policy**, and select the checkbox.
5. Click **Continue**.
Once you sign up successfully, your account is automatically logged in. Follow the on-screen instructions to create your first project and test out real-time communications.
For later visits, log in to [Agora Console](https://console.agora.io) with your phone number, email address, or linked third-party account.
### Create an Agora project [#create-an-agora-project]
To create an Agora project, do the following:
1. In [Agora Console](https://console.agora.io), open the [Projects](https://console.agora.io/project-management) page.
2. Click **Create New**.
3. Follow the on-screen instructions to enter a project name and use case, and check **Secured mode: APP ID + Token (Recommended)** as the authentication mechanism.

4. Click **Submit**. You see the new project on the **Projects** page.
### Get the App ID [#get-the-app-id]
Agora automatically assigns a unique identifier to each project, called an App ID.
To copy this App ID, find your project on the [Projects](https://console.agora.io/project-management) page in Agora Console, and click the copy icon in the **App ID** column.

## Security and authentication [#security-and-authentication]
Use the following features from your Agora account to implement security and authentication features in your apps.
### Get the App Certificate [#get-the-app-certificate]
When generating an authentication token on your app server, you need an App Certificate, in addition to the App ID.
To get an App Certificate, do the following:
1. On the [Projects](https://console.agora.io/project-management) page, click the pencil icon to edit the project you want to use.

2. Click the copy icon under **Primary Certificate**.

### Generate temporary tokens [#generate-temporary-tokens]
To ensure communication security, best practice is to use tokens to authenticate the users who log in from your app.
To generate a temporary RTC token for use in your Video SDK projects:
1. On the [Projects](https://console.agora.io/project-management) page, click the pencil icon next to your project.
2. On the **Security** panel, click **Generate Temp Token**, enter a channel name in the pop-up box and click **Generate**. Copy the generated RTC token for use in your app projects.
To generate a token for other Agora products:
1. In your browser, navigate to the [Agora token builder](https://agora-token-generator-demo.vercel.app/).
2. Choose the Agora product your user wants to log in to. Fill in **App ID** and **App Certificate** with the details of your project in Agora Console.
3. Customize the token for each user. The required fields are visible in the Agora token builder.
4. Click **Generate Token**.
The token appears in Token Builder.
5. Copy the token and use it in your app.
For more information on managing other aspects of your Agora account, see [Agora console overview](../video/reference/console-overview.mdx).
# Quickstart - create an extension (/en/realtime-media/marketplace/quickstart-implement)
Extensions are add-ons designed to rapidly extend the functionality of your app. [Extensions Marketplace](https://www.agora.io/en/agora-extensions-marketplace/) is home to extensions that make your app more fun. Extensions provide features such as Audio effects and voice changing, Face filters and background removal, and Live transcription and captioning.
In the Agora Extensions Marketplace:
* Vendors create and publish extensions to provide functionality such as audio and video processing.
* App developers use extensions to quickly implement fun and interactive functionality.
This page shows vendors how extensions work and the steps you take to develop and publish your extension to Extensions Marketplace.
## Understand the tech [#understand-the-tech]
An extension accesses voice and video data when it is captured from the user's local device, modifies it, then plays the updated data to local and remote video channels.

A typical transmission pipeline consists of a chain of procedures, including capture, pre-processing, encoding, transmitting, decoding, post-processing, and play. Audio or video extensions are inserted into either the pre-processing or post-processing procedure, in order to modify the voice or video data in the transmission pipeline.
You can currently implement the following extension types:
* **Audio**: for voice effects and noise cancellation.
* **Video**: features such as face filters and background removal.
## Vendor workflow [#vendor-workflow]
The steps to develop, test and publish an extension to Extensions Marketplace:
1. **Become a vendor**
Fill in the [vendor application form](https://www.agora.io/en/extensions-marketplace/vendor-application/), then click **APPLY NOW** .
Agora reviews your application in 7 working days and sends you the result through an email.
2. **Develop your extension**
Once your application to become a partner is approved, develop your functionality as an audio or video extension using the following documentation:
* [Develop an audio extension](build/build-your-own-extension/audio-filter.mdx)
* [Develop a video extension](build/build-your-own-extension/video-filter.mdx)
When you have developed and tested your extension, You also need to validate the user interface of your extension in a test environment. To test your extension, submit an application for testing. Agora processes your application in 7 working days and informs you through an email when it's done.
3. **Help developers easily integrate your extension into their app**.
Write the [Implementation guide](build/publish-and-document/implementation-guide.md) for your extension.
4. **Share provisioning, usage, and billing information with Agora**
* [Provisioning API](build/connect-to-agora-services/provisioning.md)
* [Usage and billing API](build/connect-to-agora-services/usage.md)
5. **Publish your extension**
When your extension is thoroughly developed and tested, you need to submit it for Agora review and provide the finalized version of your extension listing assets. For details, see [Publish your extension](build/publish-and-document/publish-extension.md).
Agora reviews your extension in 7 working days. If your extension passes the review, Agora updates the listing of your extension and informs you through an email. If your extension fails the review, Agora sends an email to report the issues you need to address.
Once you are ready to go, Agora publishes your extension in the [Extensions Marketplace](https://www.agora.io/en/agora-extensions-marketplace/), everyone can use it and you start earning.
# Quickstart - integrate an extension (/en/realtime-media/marketplace/quickstart-integrate)
<_PlatformTabsGroup groupMode="structured" canonicalPlatform="web" platforms="["android","ios","macos","web","windows","electron","flutter","react-native","unity"]" showTabs="true">
<_PlatformPanel platform="android">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="android" />
Extensions are add-ons designed to rapidly extend the functionality of your app. [Extensions Marketplace](https://www.agora.io/en/agora-extensions-marketplace/) is home to extensions that make your app more fun. Extensions provide features such as Audio effects and voice changing, Face filters and background removal, and Live transcription and captioning.
In the Agora Extensions Marketplace:
* Vendors create and publish extensions to provide functionality such as audio and video processing.
* App developers use extensions to quickly implement fun and interactive functionality.
This page shows you how to integrate an extension from Agora Extensions Marketplace into your app. There can be specific guidance for each extension.
## Understand the tech [#understand-the-tech]
An extension accesses voice and video data when it is captured from the user's local device, modifies it, then plays the updated data to local and remote video channels.
**Extension call workflow**

A typical transmission pipeline consists of a chain of procedures, including capture, pre-processing, encoding, transmitting, decoding, post-processing, and play. Audio or video extensions are inserted into either the pre-processing or post-processing procedure, in order to modify the voice or video data in the transmission pipeline.
## Prerequisites [#prerequisites]
To test the code used in this page you need to have:
* An Agora [account](/en/realtime-media/marketplace/manage-agora-account) and [project](/en/realtime-media/marketplace/manage-agora-account).
* A computer with Internet access.
Ensure that no firewall is blocking your network communication.
* Implemented the [SDK quickstart](/en/realtime-media/video/quickstart).
## Project setup [#project-setup]
In order to integrate an extension into your project:
1. **Activate an extension**
2. Log in to [Agora Console](https://console.agora.io/v2).
3. In the left navigation panel, click **Extension Marketplace**, then click the extension you want to activate.
You are now on the extension detail page.
3. Select a pricing plan and click **Buy and Activate**.
* If you have already created an Agora project:
The **Projects** section appears and lists all of your projects.
* If you have not created an Agora project:
[Create a new project](/en/realtime-media/marketplace/manage-agora-account), the project appears in the **Projects** section.
4. Under **Projects** on the extension detail page, find the project in which you want to use the extension, then turn on the switch in the **Action** column.
5. **Get the apiKey and apiSecret for the extension**
If required for the extension, to get the extension apiKey and apiSecret, in the **Projects** extension detail page, click **View** in the **Secret** column.
3. **Download the extension**
In the extension detail page, click **Download**, then unzip the extension in a local directory.
4. **Install the extension in your project**
* Android Archive file (`.aar`)
1. Save the extension `.aar` file to `/app/libs` in your project.
2. In `/Gradle Scripts/build.gradle(Module: app)`, add the following line under `dependencies`:
```java
implementation fileTree(include: ['*.jar', '*.aar'], dir: 'libs')
```
* Shared Library (`.so`)
Save the `.so` file to the following paths in your project:
1. `/app/src/main/jniLibs/arm64-v8a`
2. `/app/src/main/jniLibs/armeabi-v7a`
You are now ready to integrate the extension in your app.
## Integrate the extension into your project [#integrate-the-extension-into-your-project]
The watermark extension adds a watermark on video streamed to your local client. This section shows you how to implement the watermark extension
in your Agora project:
1. **Import the necessary classes**
2. Download the [watermark extension](https://web-cdn.agora.io/docs-files/1630400262363) and follow the steps for `.aar` files in [setup](#project-setup).
3. In `app/src/main/java/com.example./MainActivity`:
4. Add the following lines to import the Android classes used by the extension:
Java
Kotlin
```java
import org.json.JSONException;
import org.json.JSONObject;
```
```kotlin
import org.json.JSONException
import org.json.JSONObject
```
2. Add the following lines to import the Agora classes used by the extension:
Java
Kotlin
```java
// ExtensionManager is used to pass in basic info about the extension
import io.agora.extension.ExtensionManager;
import io.agora.rtc2.IMediaExtensionObserver;
```
```kotlin
// ExtensionManager is used to pass in basic info about the extension
import io.agora.extension.ExtensionManager
import io.agora.rtc2.IMediaExtensionObserver
```
2. **Add the extension and register the event handler**
In `setupVideoSDKEngine`, add the following code before `agoraEngine = RtcEngine.create(config);`:
Java
Kotlin
```java
config.addExtension(ExtensionManager.EXTENSION_NAME);
// Register IMediaExtensionObserver to receive events from the extension.
config.mExtensionObserver = new IMediaExtensionObserver() {
@Override
public void onEvent(String vendor, String extension, String key, String value) {
// Add callback handling logics for extension events.
showMessage("Extension: " + extension + "
Key: " + key + "
Value:" + value);
}
@Override
public void onStarted(String provider, String extension) {
showMessage("Extension started");
}
@Override
public void onStopped(String provider, String extension) {
showMessage("Extension stopped");
}
@Override
public void onError(String provider, String extension, int error, String message) {
showMessage(message);
}
};
```
```kotlin
config.addExtension(ExtensionManager.EXTENSION_NAME)
// Register IMediaExtensionObserver to receive events from the extension.
config.mExtensionObserver = object : IMediaExtensionObserver {
override fun onEvent(vendor: String, extension: String, key: String, value: String) {
// Add callback handling logics for extension events.
showMessage("Extension: $extension
Key: $key
Value: $value")
}
override fun onStarted(provider: String, extension: String) {
showMessage("Extension started")
}
override fun onStopped(provider: String, extension: String) {
showMessage("Extension stopped")
}
override fun onError(provider: String, extension: String, error: Int, message: String) {
showMessage(message)
}
}
```
3. **Enable the extension**
Call `enableExtension` to enable the extension. To enable multiple extensions, call `enableExtension` as many times. The sequence of enabling multiple extensions determines the order of these extensions in the transmission pipeline. For example, if you enable extension A before extension B, extension A processes data from the SDK before extension B.
In `setupVideoSDKEngine`, add the following code before `agoraEngine = RtcEngine.create(config);`:
Java
Kotlin
```java
agoraEngine.enableExtension(ExtensionManager.EXTENSION_VENDOR_NAME, ExtensionManager.EXTENSION_VIDEO_FILTER_NAME, true);
```
```kotlin
agoraEngine.enableExtension(
ExtensionManager.EXTENSION_VENDOR_NAME,
ExtensionManager.EXTENSION_VIDEO_FILTER_NAME,
true
)
```
4. **Set extension properties**
In the `joinChannel(View view)` method, add the following code after `agoraEngine.joinChannel`:
Java
Kotlin
```java
JSONObject o = new JSONObject();
try {
// Pass in the key-value pairs defined by the extension provider to configure the feature you want to use.
o.put("plugin.watermark.wmStr", "Agora");
o.put("plugin.watermark.wmEffectEnabled", true);
// Call setExtensionProperty to use the watermark feature.
agoraEngine.setExtensionProperty(ExtensionManager.EXTENSION_VENDOR_NAME,
ExtensionManager.EXTENSION_VIDEO_FILTER_NAME, "key", o.toString());
} catch (JSONException e) {
e.printStackTrace();
}
```
```kotlin
val o = JSONObject()
try {
// Pass in the key-value pairs defined by the extension provider to configure the feature you want to use.
o.put("plugin.watermark.wmStr", "Agora")
o.put("plugin.watermark.wmEffectEnabled", true)
// Call setExtensionProperty to use the watermark feature.
agoraEngine.setExtensionProperty(
ExtensionManager.EXTENSION_VENDOR_NAME,
ExtensionManager.EXTENSION_VIDEO_FILTER_NAME,
"key",
o.toString()
)
} catch (e: JSONException) {
e.printStackTrace()
}
```
## Test your implementation [#test-your-implementation]
To ensure that you have integrated the extension in your app:
1. Connect the Android device to the computer.
2. Click `Run app` on your Android Studio. A moment later you will see the project installed on your device.
3. When the app launches, you can see yourself and the watermark `Agora` on the local view.
## Reference [#reference]
This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.
### API reference [#api-reference]
* [`RtcEngineConfig.addExtension`](https://api-ref.agora.io/en/video-sdk/android/4.x/API/rtc_api_data_type.html#api_irtcengine_addextension)
* [`enableExtension`](https://api-ref.agora.io/en/video-sdk/android/4.x/API/class_irtcengine.html#api_irtcengine_enableextension)
* [`getExtensionProperty`](https://api-ref.agora.io/en/video-sdk/android/4.x/API/class_irtcengine.html#api_irtcengine_getextensionproperty)
* [`setExtensionProperty`](https://api-ref.agora.io/en/video-sdk/android/4.x/API/class_irtcengine.html#api_irtcengine_setextensionproperty)
* [`IMediaExtensionObserver`](https://api-ref.agora.io/en/video-sdk/android/4.x/API/class_imediaextensionobserver.html)
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="ios">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="ios" />
Extensions are add-ons designed to rapidly extend the functionality of your app. [Extensions Marketplace](https://www.agora.io/en/agora-extensions-marketplace/) is home to extensions that make your app more fun. Extensions provide features such as Audio effects and voice changing, Face filters and background removal, and Live transcription and captioning.
In the Agora Extensions Marketplace:
* Vendors create and publish extensions to provide functionality such as audio and video processing.
* App developers use extensions to quickly implement fun and interactive functionality.
This page shows you how to integrate an extension from Agora Extensions Marketplace into your app. There can be specific guidance for each extension.
## Understand the tech [#understand-the-tech-1]
An extension accesses voice and video data when it is captured from the user's local device, modifies it, then plays the updated data to local and remote video channels.
**Extension call workflow**

A typical transmission pipeline consists of a chain of procedures, including capture, pre-processing, encoding, transmitting, decoding, post-processing, and play. Audio or video extensions are inserted into either the pre-processing or post-processing procedure, in order to modify the voice or video data in the transmission pipeline.
## Prerequisites [#prerequisites-1]
To test the code used in this page you need to have:
* An Agora [account](/en/realtime-media/marketplace/manage-agora-account) and [project](/en/realtime-media/marketplace/manage-agora-account).
* A computer with Internet access.
Ensure that no firewall is blocking your network communication.
* Implemented the [SDK quickstart](/en/realtime-media/video/quickstart).
## Project setup [#project-setup-1]
In order to integrate an extension into your project:
1. **Activate an extension**
2. Log in to [Agora Console](https://console.agora.io/v2).
3. In the left navigation panel, click **Extension Marketplace**, then click the extension you want to activate.
You are now on the extension detail page.
3. Select a pricing plan and click **Buy and Activate**.
* If you have already created an Agora project:
The **Projects** section appears and lists all of your projects.
* If you have not created an Agora project:
[Create a new project](/en/realtime-media/marketplace/manage-agora-account), the project appears in the **Projects** section.
4. Under **Projects** on the extension detail page, find the project in which you want to use the extension, then turn on the switch in the **Action** column.
5. **Get the apiKey and apiSecret for the extension**
If required for the extension, to get the extension apiKey and apiSecret, in the **Projects** extension detail page, click **View** in the **Secret** column.
3. **Download the extension**
In the extension detail page, click **Download**, then unzip the extension in a local directory.
4. **Install the extension in your project**
5. Save the extension `.framework` or `.xcframwork` files to your project folder.
6. In Xcode, open your project and select your target. Go to **General** > **Frameworks, Libraries, and Embedded Content**.
7. Click **+** > **Add Other…** > **Add Files** and select the `.framework` or `.xcframwork` files you saved earlier.
You are now ready to integrate the extension in your app.
## Integrate the extension into your project [#integrate-the-extension-into-your-project-1]
To use an extension in your Agora project:
* The implementation procedure varies according to extensions. Each extension vendor provides their own implementation guides, which is validated by Agora before the official release of the extension.
* To implement the extension in your project, go to the detail page of the extension on [Agora Console](https://console.agora.io/v2), click **Implementation guides**, and follow the steps on the page.
## Test your implementation [#test-your-implementation-1]
To ensure that you have integrated the extension in your app:
1. Connect a device to the computer.
2. In Xcode, Click `Run`. A moment later you will see the project installed on your device.
3. Connect to a channel and test your effect.
Agora provides an open-source sample project [SimpleFilter](https://github.com/AgoraIO/API-Examples/tree/main/iOS/APIExample/SimpleFilter) on GitHub for your reference.
## Reference [#reference-1]
This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="macos">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="macos" />
Extensions are add-ons designed to rapidly extend the functionality of your app. [Extensions Marketplace](https://www.agora.io/en/agora-extensions-marketplace/) is home to extensions that make your app more fun. Extensions provide features such as Audio effects and voice changing, Face filters and background removal, and Live transcription and captioning.
In the Agora Extensions Marketplace:
* Vendors create and publish extensions to provide functionality such as audio and video processing.
* App developers use extensions to quickly implement fun and interactive functionality.
This page shows you how to integrate an extension from Agora Extensions Marketplace into your app. There can be specific guidance for each extension.
## Understand the tech [#understand-the-tech-2]
An extension accesses voice and video data when it is captured from the user's local device, modifies it, then plays the updated data to local and remote video channels.
**Extension call workflow**

A typical transmission pipeline consists of a chain of procedures, including capture, pre-processing, encoding, transmitting, decoding, post-processing, and play. Audio or video extensions are inserted into either the pre-processing or post-processing procedure, in order to modify the voice or video data in the transmission pipeline.
## Prerequisites [#prerequisites-2]
To test the code used in this page you need to have:
* An Agora [account](/en/realtime-media/marketplace/manage-agora-account) and [project](/en/realtime-media/marketplace/manage-agora-account).
* A computer with Internet access.
Ensure that no firewall is blocking your network communication.
* Implemented the [SDK quickstart](/en/realtime-media/video/quickstart).
## Project setup [#project-setup-2]
In order to integrate an extension into your project:
1. **Activate an extension**
2. Log in to [Agora Console](https://console.agora.io/v2).
3. In the left navigation panel, click **Extension Marketplace**, then click the extension you want to activate.
You are now on the extension detail page.
3. Select a pricing plan and click **Buy and Activate**.
* If you have already created an Agora project:
The **Projects** section appears and lists all of your projects.
* If you have not created an Agora project:
[Create a new project](/en/realtime-media/marketplace/manage-agora-account), the project appears in the **Projects** section.
4. Under **Projects** on the extension detail page, find the project in which you want to use the extension, then turn on the switch in the **Action** column.
5. **Get the apiKey and apiSecret for the extension**
If required for the extension, to get the extension apiKey and apiSecret, in the **Projects** extension detail page, click **View** in the **Secret** column.
3. **Download the extension**
In the extension detail page, click **Download**, then unzip the extension in a local directory.
4. **Install the extension in your project**
5. Save the extension `.framework` or `.xcframwork` files to your project folder.
6. In Xcode, open your project and select your target. Go to **General** > **Frameworks, Libraries, and Embedded Content**.
7. Click **+** > **Add Other…** > **Add Files** and select the `.framework` or `.xcframwork` files you saved earlier.
You are now ready to integrate the extension in your app.
## Integrate the extension into your project [#integrate-the-extension-into-your-project-2]
To use an extension in your Agora project:
* The implementation procedure varies according to extensions. Each extension vendor provides their own implementation guides, which is validated by Agora before the official release of the extension.
* To implement the extension in your project, go to the detail page of the extension on [Agora Console](https://console.agora.io/v2), click **Implementation guides**, and follow the steps on the page.
## Test your implementation [#test-your-implementation-2]
To ensure that you have integrated the extension in your app:
1. In Xcode, Click `Run`. A moment later you will see the project installed on your device.
2. Connect to a channel and test your effect.
## Reference [#reference-2]
This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="web">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="web" />
Extensions are add-ons designed to rapidly extend the functionality of your app. [Extensions Marketplace](https://www.agora.io/en/agora-extensions-marketplace/) is home to extensions that make your app more fun. Extensions provide features such as Audio effects and voice changing, Face filters and background removal, and Live transcription and captioning.
In the Agora Extensions Marketplace:
* Vendors create and publish extensions to provide functionality such as audio and video processing.
* App developers use extensions to quickly implement fun and interactive functionality.
This page shows you how to integrate an extension from Agora Extensions Marketplace into your app. There can be specific guidance for each extension.
## Understand the tech [#understand-the-tech-3]
An extension accesses voice and video data when it is captured from the user's local device, modifies it, then plays the updated data to local and remote video channels.
**Extension call workflow**

A typical transmission pipeline consists of a chain of procedures, including capture, pre-processing, encoding, transmitting, decoding, post-processing, and play. Audio or video extensions are inserted into either the pre-processing or post-processing procedure, in order to modify the voice or video data in the transmission pipeline.
## Prerequisites [#prerequisites-3]
To test the code used in this page you need to have:
* An Agora [account](/en/realtime-media/marketplace/manage-agora-account) and [project](/en/realtime-media/marketplace/manage-agora-account).
* A computer with Internet access.
Ensure that no firewall is blocking your network communication.
* Implemented the [SDK quickstart](/en/realtime-media/video/quickstart).
- A [supported browser](/en/realtime-media/marketplace/reference/supported-platforms).
- Physical media input devices, such as a camera and a microphone.
- A JavaScript package manager such as [npm](https://www.npmjs.com/package/npm).
## Project setup [#project-setup-3]
In order to integrate an extension into your project:
1. **Activate an extension**
2. Log in to [Agora Console](https://console.agora.io/v2).
3. In the left navigation panel, click **Extension Marketplace**, then click the extension you want to activate.
You are now on the extension detail page.
3. Select a pricing plan and click **Buy and Activate**.
* If you have already created an Agora project:
The **Projects** section appears and lists all of your projects.
* If you have not created an Agora project:
[Create a new project](/en/realtime-media/marketplace/manage-agora-account), the project appears in the **Projects** section.
4. Under **Projects** on the extension detail page, find the project in which you want to use the extension, then turn on the switch in the **Action** column.
5. **Get the apiKey and apiSecret for the extension**
If required for the extension, to get the extension apiKey and apiSecret, in the **Projects** extension detail page, click **View** in the **Secret** column.
3. **Download the extension**
In the extension detail page, click **Download**, you are taken to the npm page for the extension.
4. **Install the extension in your project**
5. In the extension npm page, copy the **Install** command.
6. In the root of your project, install the extension package. For example, for AI Noise Suppression:
```bash
npm i agora-extension-ai-denoiser
```
You are now ready to integrate the extension in your app.
## Integrate the extension into your project [#integrate-the-extension-into-your-project-3]
To use an extension in your Agora project:
* The implementation procedure varies according to extensions. Each extension vendor provides their own implementation guides, which is validated by Agora before the official release of the extension.
* To implement the extension in your project, go to the detail page of the extension on [Extensions Marketplace](https://console.agora.io/v2/extensions-marketplace), click **Implementation guides**, and follow the steps on the page.
To learn how to use a Web extension, see [AI Noise Suppression](/en/realtime-media/video/build/enhance-the-audio-experience/ai-noise-suppression).
## Test your implementation [#test-your-implementation-3]
To ensure that you have integrated the extension in your app:
1. [Generate a temporary token](/en/realtime-media/marketplace/manage-agora-account) in Agora Console.
2. In your browser, navigate to the [Agora web demo](https://webdemo.agora.io/basicVideoCall/index.html) and update *App ID*, *Channel*, and *Token* with the values for your temporary token, then click **Join**.
3. In **main.js**, update `appID`, `channel` and `token` with your values.
4. Start the dev server
Execute the following command in the terminal:
```bash
npm run dev
```
Use the URL displayed in the terminal to open the app in your browser.
5. Try out the extension that you have integrated.
## Reference [#reference-3]
This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.
### API reference [#api-reference-1]
* [`AgoraRTC.registerExtensions`](https://api-ref.agora.io/en/video-sdk/web/4.x/interfaces/iagorartc.html#registerextensions)
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="windows">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="windows" />
Extensions are add-ons designed to rapidly extend the functionality of your app. [Extensions Marketplace](https://www.agora.io/en/agora-extensions-marketplace/) is home to extensions that make your app more fun. Extensions provide features such as Audio effects and voice changing, Face filters and background removal, and Live transcription and captioning.
In the Agora Extensions Marketplace:
* Vendors create and publish extensions to provide functionality such as audio and video processing.
* App developers use extensions to quickly implement fun and interactive functionality.
This page shows you how to integrate an extension from Agora Extensions Marketplace into your app. There can be specific guidance for each extension.
## Understand the tech [#understand-the-tech-4]
An extension accesses voice and video data when it is captured from the user's local device, modifies it, then plays the updated data to local and remote video channels.
**Extension call workflow**

A typical transmission pipeline consists of a chain of procedures, including capture, pre-processing, encoding, transmitting, decoding, post-processing, and play. Audio or video extensions are inserted into either the pre-processing or post-processing procedure, in order to modify the voice or video data in the transmission pipeline.
## Prerequisites [#prerequisites-4]
To test the code used in this page you need to have:
* An Agora [account](/en/realtime-media/marketplace/manage-agora-account) and [project](/en/realtime-media/marketplace/manage-agora-account).
* A computer with Internet access.
Ensure that no firewall is blocking your network communication.
* Same setup as the [Video SDK quickstart prerequisites](/en/realtime-media/video/quickstart) for Video Calling.
## Project setup [#project-setup-4]
In order to integrate an extension into your project:
1. **Activate an extension**
2. Log in to [Agora Console](https://console.agora.io/v2).
3. In the left navigation panel, click **Extension Marketplace**, then click the extension you want to activate.
You are now on the extension detail page.
3. Select a pricing plan and click **Buy and Activate**.
* If you have already created an Agora project:
The **Projects** section appears and lists all of your projects.
* If you have not created an Agora project:
[Create a new project](/en/realtime-media/marketplace/manage-agora-account), the project appears in the **Projects** section.
4. Under **Projects** on the extension detail page, find the project in which you want to use the extension, then turn on the switch in the **Action** column.
5. **Get the apiKey and apiSecret for the extension**
If required for the extension, to get the extension apiKey and apiSecret, in the **Projects** extension detail page, click **View** in the **Secret** column.
3. **Download the extension**
In the extension detail page, click **Download**, then unzip the extension in a local directory.
4. **Install the extension in your project**
Add the extension `.dll` to your project.
You are now ready to integrate the extension in your app.
## Integrate the extension into your project [#integrate-the-extension-into-your-project-4]
Integrating an extension into your project involves a few important steps to ensure seamless interaction between the extension and Agora Video SDK. Each of these steps plays a crucial role to ensure that the extension works correctly and efficiently within your Agora project:
1. **Define variables to integrate an extension with Agora Video SDK**
In `AgoraImplementationDlg.h`, add the following declarations to the `CAgoraImplementationDlg` class before `setupVideoSDKEngine();`:
```cpp
// File path to the dynamic library (.dll file on Windows) that contains the implementation of the extension provider.
const char* extensionLibrarayPath = ""; // For example : "/ library / libagora_segmentation_extension.dll"
const char * extensionProvider = ""; // For example : "agora.io";
const char* extensionName = ""; // For example : "libagora_segmentation_extension.dll";
// The key is a string that identifies a specific property of the extension,
// and value is the new value for that property.
// What these keys are and what values they can take is entirely up to the extension.
// For example, an extension might have a property that controls the quality of a video stream, with a key named "videoQuality".
// To set this property to "high", you would call setExtensionProperty() with "videoQuality" as the key and "high" as the value.
const char* extensionKey = "";
const char* extensionValue = "";
// When using methods that take a MEDIA_SOURCE_TYPE parameter, you would specify the value that corresponds to the type of media source
// you want the method to operate on. For example, if you want to use the second camera to capture video, set this parameter to "SECONDARY_CAMERA_SOURCE".
// The default value is UNKNOWN_MEDIA_SOURCE.
agora::media::MEDIA_SOURCE_TYPE extensionMediaType = agora::media::UNKNOWN_MEDIA_SOURCE; // insert the media souce type as per your context
```
2. **Register extension callbacks to `IRtcEngineEventHandler` event handler**
3. In `AgoraImplementationDlg.h`, add the following declarations to the `AgoraEventHandler` class after `onLeaveChannel`:
```cpp
virtual void onExtensionEvent(const char* provider, const char* extension, const char* key, const char* value)override;
virtual void onExtensionStopped(const char* provider, const char* extension)override;
virtual void onExtensionStarted(const char* provider, const char* extension)override;
virtual void onExtensionError(const char* provider, const char* extension, int error, const char* errorMessage)override;
```
4. Define these callbacks in `AgoraImplementationDlg.cpp`:
```cpp
// Extension Callbacks
void AgoraEventHandler::onExtensionEvent(const char* provider, const char* extension, const char* key, const char* value)
{
// Handle the event
CString extension_provider(provider);
CString extension_name(extension);
CString extension_key(key);
CString extension_value(value);
CString message;
message.Format(_T("Extension Provider: %s\nExtension Name: %s\nKey: %s\nValue: %s\n is enabled to run."), extension_provider, extension_name);
AfxMessageBox(message);
}
void AgoraEventHandler::onExtensionStopped(const char* provider, const char* extension)
{
AfxMessageBox(L"Extension stopped.");
}
void AgoraEventHandler::onExtensionStarted(const char* provider, const char* extension)
{
AfxMessageBox(Extension started);
}
void AgoraEventHandler::onExtensionError(const char* provider, const char* extension, int error, const char* errorMessage)
{
// Handle the error event
CString error_message(errorMessage);
CString message;
message.Format(_T("Error: %s\n with error code %d"), error_message, error);
AfxMessageBox(message);
}
```
5. **Enable the extension and set the extension properties**
To use the extension you downloaded in your app:
1. Load the extension.
2. Register the extension.
3. Enable the extension.
4. Set the extension property.
To accomplish these tasks, do the following:
1. In `AgoraImplementationDlg.h`, declare `setupMarketExtension()` before `setupVideoSDKEngine();`:
```cpp
// Function to load, register and enable the extension. Here you also set the extension propertties for your specific extension
void setupMarketExtension(IRtcEngine* agoraEngine);
```
2. In `AgoraImplementationDlg.cpp`, put the following in the `setupVideoSDKEngine` function just after
initializing Agora Engine `agoraEngine->initialize(context);`:
```cpp
// Integrate extension to the Agora SDK.
setupMarketExtension(agoraEngine);
```
3. Define `setupMarketExtension()`:
```cpp
void CAgoraImplementationDlg::setupMarketExtension(IRtcEngine* agoraEngine)
{
// Load the extension with the file path to the dynamic library (.dll file on Windows) and a boolean flag 'unload_after_use' is true here mean
// unload the dynamic library after it has been used.
agoraEngine->loadExtensionProvider(extensionLibrarayPath,true);
// Register the extension
agoraEngine->registerExtension(extensionProvider, extensionName, extensionMediaType);
// Enable the extension
agoraEngine->enableExtension(extensionProvider, extensionName, true, extensionMediaType);
// Set Extension property. The extension key and extension value would be specific to the extension you are using.
// This function is used to set the value of a specific property of an extension
agoraEngine->setExtensionProperty(extensionProvider, extensionName, extensionKey, extensionValue, extensionMediaType);
}
```
6. **Disable the extension before closing the application**
You must unload and disable all extensions before the app closes. As you set `unload_after_use` to
`true` in `loadExtensionProvider()`, the extension dynamic link library unloads automatically after it has
been used. You disable the extension to prevent any potential memory leaks or unanticipated
effects. To do this, put the following in `OnClose()` before `agoraEngine->release();`:
```cpp
// Disable extension by making 'eanbled' flag as false .
agoraEngine->enableExtension(extensionProvider, extensionName, false);
```
## Test your implementation [#test-your-implementation-4]
To ensure that you have integrated the extension in your app:
1. [Generate a temporary token](/en/realtime-media/marketplace/manage-agora-account) in Agora Console .
2. In your browser, navigate to the [Agora web demo](https://webdemo.agora.io/basicVideoCall/index.html) and update *App ID*, *Channel*, and *Token* with the values for your temporary token, then click **Join**.
3. In `CAgoraImplementationDlg.h`, update `appId`, `channelName` and `token` with the values for your temporary token.
4. Set the values for variables and properties in the framework code for your extension in use.
5. In Visual Studio, click **Local Windows Debugger**. A moment later you see the project running on your development device.
If this is the first time you run the project, you need to grant microphone and camera access to your app.
6. Experience the features of your new extension.
## Reference [#reference-4]
This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.
### API reference [#api-reference-2]
* [`loadExtensionProvider`](https://api-ref.agora.io/en/video-sdk/cpp/4.x/API/class_irtcengine.html#api_irtcengine_loadextensionprovider)
* [`registerExtension`](https://api-ref.agora.io/en/video-sdk/cpp/4.x/API/class_irtcengine.html#api_irtcengine_registerextension)
* [`enableExtension`](https://api-ref.agora.io/en/video-sdk/cpp/4.x/API/class_irtcengine.html#api_irtcengine_enableextension)
* [`setExtensionProperty`](https://api-ref.agora.io/en/video-sdk/cpp/4.x/API/class_irtcengine.html#api_irtcengine_setextensionproperty)
* [`MEDIA_SOURCE_TYPE`](https://api-ref.agora.io/en/video-sdk/cpp/4.x/API/enum_mediasourcetype.html)
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="electron">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="electron" />
Extensions are add-ons designed to rapidly extend the functionality of your app. [Extensions Marketplace](https://www.agora.io/en/agora-extensions-marketplace/) is home to extensions that make your app more fun. Extensions provide features such as Audio effects and voice changing, Face filters and background removal, and Live transcription and captioning.
In the Agora Extensions Marketplace:
* Vendors create and publish extensions to provide functionality such as audio and video processing.
* App developers use extensions to quickly implement fun and interactive functionality.
This page shows you how to integrate an extension from Agora Extensions Marketplace into your app. There can be specific guidance for each extension.
## Understand the tech [#understand-the-tech-5]
An extension accesses voice and video data when it is captured from the user's local device, modifies it, then plays the updated data to local and remote video channels.
**Extension call workflow**

A typical transmission pipeline consists of a chain of procedures, including capture, pre-processing, encoding, transmitting, decoding, post-processing, and play. Audio or video extensions are inserted into either the pre-processing or post-processing procedure, in order to modify the voice or video data in the transmission pipeline.
## Prerequisites [#prerequisites-5]
To test the code used in this page you need to have:
* An Agora [account](/en/realtime-media/marketplace/manage-agora-account) and [project](/en/realtime-media/marketplace/manage-agora-account).
* A computer with Internet access.
Ensure that no firewall is blocking your network communication.
- Physical media input devices, such as a camera and a microphone.
- A JavaScript package manager such as [npm](https://www.npmjs.com/package/npm).
## Project setup [#project-setup-5]
In order to integrate an extension into your project:
1. **Activate an extension**
2. Log in to [Agora Console](https://console.agora.io/v2).
3. In the left navigation panel, click **Extension Marketplace**, then click the extension you want to activate.
You are now on the extension detail page.
3. Select a pricing plan and click **Buy and Activate**.
* If you have already created an Agora project:
The **Projects** section appears and lists all of your projects.
* If you have not created an Agora project:
[Create a new project](/en/realtime-media/marketplace/manage-agora-account), the project appears in the **Projects** section.
4. Under **Projects** on the extension detail page, find the project in which you want to use the extension, then turn on the switch in the **Action** column.
5. **Get the apiKey and apiSecret for the extension**
If required for the extension, to get the extension apiKey and apiSecret, in the **Projects** extension detail page, click **View** in the **Secret** column.
You are now ready to integrate the extension in your app.
## Integrate the extension into your project [#integrate-the-extension-into-your-project-5]
This section shows you how to implement the video filter extension in your app:
1. **Add the required variables**
To integrate the extension, in `preload.js`, add the following variables to declarations:
```javascript
var path = "agora_segmentation_extension";
var provider = "agora_video_filters_segmentation";
var extension = "portrait_segmentation";
var enableExtension = false;
```
2. **Implement the logic to load and enable the extension**
To load the extension in your app, call `loadExtensionProvider` and pass the extension path. To enable the extension, call `enableExtension` and pass the provider name and extension name. To implement this workflow, in `preload.js`, add the following method before `window.onload = () =>`:
```javascript
function enableExtension()
{
if (!path) {
console.log('path is invalid');
return;
}
if (!provider) {
console.log('provider is invalid');
return;
}
if (!extension) {
console.log('extension is invalid');
return;
}
agoraEngine.loadExtensionProvider(path);
agoraEngine.enableExtension(provider, extension, enableExtension);
};
```
2. **Implement the logic to disable the extension**
To disable the extension, call `enableExtension` and pass the `provider`, `extension`, and `enableExtension` variables as parameters. The `enableExtension` variable is set to `false` to disable the extension. To implement this logic, in `preload.js`, add the following method before `window.onload = () =>`:
```javascript
function disableExtension()
{
agoraEngine.enableExtension(provider, extension, enableExtension);
};
```
3. **Setup an event listener to enable and disable the extension with a button**
When the user presses **Enable Extension**, your app loads and starts the extension with a call to the `enableExtension` method. When the user presses the button again, your app disables the extension with a call to the `disableExtension` method. To implement this workflow, in `preload.js`, add the following method before `document.getElementById("leave").onclick = async function ()`:
```javascript
document.getElementById("extension").onclick = async function ()
{
enableExtension = !enableExtension;
if(isExtension)
{
enableExtension();
document.getElementById("extension").innerHTML = "Disable Extension";
}
{
disableExtension();
document.getElementById("extension").innerHTML = "Enable Extension";
}
}
```
4. **Implement the required callbacks to manage the extension**
Video SDK provides the following callbacks to manage an extension:
* `onExtensionStarted`: Occurs when the extension is enabled.
* `onExtensionError`: Occurs when the extension runs incorrectly.
* `onExtensionStopped`: Occurs when the extension is disabled.
* `onExtensionEvent`: The event callback of the extension.
To implement these callbacks, in `preload.js`, add the following callbacks before `onUserJoined`:
```javascript
onExtensionError:( provider, extName, error, msg) =>
{
console.log("Error :" + error, "Error detail :" + msg);
},
onExtensionEvent:( provider, extName, key, value) =>
{
},
onExtensionStarted:(provider, extName) =>
{
},
onExtensionStopped:(provider, extName) =>
{
}
```
## Test your implementation [#test-your-implementation-5]
To ensure that you have integrated the extension in your app:
1. In **preload.js**, update `appID`, `channel` and `token` with your values.
2. Run the app
Execute the following command in the terminal:
```bash
npm start
```
You see your app opens a window named **Get started with Video Calling**.
3. Press **Enable Extension**. Your app start using the Agora video filter extension.
## Reference [#reference-5]
This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.
### API reference [#api-reference-3]
* [`loadExtensionProvider`](https://api-ref.agora.io/en/video-sdk/electron/4.x/API/class_irtcengine.html#api_irtcengine_loadextensionprovider)
* [`setExtensionProperty`](https://api-ref.agora.io/en/video-sdk/electron/4.x/API/class_irtcengine.html#api_irtcengine_setextensionproperty)
* [`enableExtension`](https://api-ref.agora.io/en/video-sdk/electron/4.x/API/class_irtcengine.html#api_irtcengine_enableextension)
* [`onExtensionStarted`](https://api-ref.agora.io/en/video-sdk/electron/4.x/API/class_irtcengineeventhandler.html#callback_irtcengineeventhandler_onextensionstarted)
* [`onExtensionStopped`](https://api-ref.agora.io/en/video-sdk/electron/4.x/API/class_irtcengineeventhandler.html#callback_irtcengineeventhandler_onextensionstopped)
* [`onExtensionError`](https://api-ref.agora.io/en/video-sdk/electron/4.x/API/class_irtcengineeventhandler.html#callback_irtcengineeventhandler_onextensionerror)
* [`onExtensionEvent`](https://api-ref.agora.io/en/video-sdk/electron/4.x/API/class_irtcengineeventhandler.html#callback_irtcengineeventhandler_onextensionevent)
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="flutter">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="flutter" />
Extensions are add-ons designed to rapidly extend the functionality of your app. [Extensions Marketplace](https://www.agora.io/en/agora-extensions-marketplace/) is home to extensions that make your app more fun. Extensions provide features such as Audio effects and voice changing, Face filters and background removal, and Live transcription and captioning.
In the Agora Extensions Marketplace:
* Vendors create and publish extensions to provide functionality such as audio and video processing.
* App developers use extensions to quickly implement fun and interactive functionality.
This page shows you how to integrate an extension from Agora Extensions Marketplace into your app. There can be specific guidance for each extension.
## Understand the tech [#understand-the-tech-6]
An extension accesses voice and video data when it is captured from the user's local device, modifies it, then plays the updated data to local and remote video channels.
**Extension call workflow**

A typical transmission pipeline consists of a chain of procedures, including capture, pre-processing, encoding, transmitting, decoding, post-processing, and play. Audio or video extensions are inserted into either the pre-processing or post-processing procedure, in order to modify the voice or video data in the transmission pipeline.
## Prerequisites [#prerequisites-6]
To test the code used in this page you need to have:
* An Agora [account](/en/realtime-media/marketplace/manage-agora-account) and [project](/en/realtime-media/marketplace/manage-agora-account).
* A computer with Internet access.
Ensure that no firewall is blocking your network communication.
* Implemented the [SDK quickstart](/en/realtime-media/video/quickstart).
- [Flutter](https://docs.flutter.dev/get-started/install) 2.0.0 or higher
- Dart 2.15.1 or higher
- [Android Studio](https://developer.android.com/studio), IntelliJ, VS Code, or any other IDE that supports Flutter, see [Set up an editor](https://docs.flutter.dev/get-started/editor).
- If your target platform is iOS:
- Xcode on macOS (latest version recommended)
- A physical iOS device
- iOS version 12.0 or later
- If your target platform is Android:
- Android Studio on macOS or Windows (latest version recommended)
- An Android emulator or a physical Android device.
- If you are developing a desktop application for Windows, macOS or Linux, make sure your development device meets the [Flutter desktop development requirements](https://docs.flutter.dev/development/platform-integration/desktop).
## Project setup [#project-setup-6]
In order to integrate an extension into your project:
1. **Activate an extension**
2. Log in to [Agora Console](https://console.agora.io/v2).
3. In the left navigation panel, click **Extension Marketplace**, then click the extension you want to activate.
You are now on the extension detail page.
3. Select a pricing plan and click **Buy and Activate**.
* If you have already created an Agora project:
The **Projects** section appears and lists all of your projects.
* If you have not created an Agora project:
[Create a new project](/en/realtime-media/marketplace/manage-agora-account), the project appears in the **Projects** section.
4. Under **Projects** on the extension detail page, find the project in which you want to use the extension, then turn on the switch in the **Action** column.
5. **Get the apiKey and apiSecret for the extension**
If required for the extension, to get the extension apiKey and apiSecret, in the **Projects** extension detail page, click **View** in the **Secret** column.
3. Open your Flutter project
Load the [SDK quickstart](/en/realtime-media/video/quickstart) Video Calling project you created previously.
4. Get the extension
Visit the [Agora Extensions Marketplace](https://www.agora.io/en/agora-extensions-marketplace/) and follow the procedure to download and install the desired extension.
You are now ready to integrate the extension in your app.
## Integrate the extension into your project [#integrate-the-extension-into-your-project-6]
This section presents the framework code you add to your Flutter project to integrate an extension.
1. **Load the extension provider**
You call `loadExtensionProvider` during Agora Engine initialization to specify the extension library path. To do this, add the following code to the `setupVideoSDKEngine` method after you initialize the engine with `agoraEngine.initialize`:
```dart
agoraEngine.loadExtensionProvider("");
```
2. **Enable the extension**
To enable the extension, add the following code to the `setupVideoSDKEngine` method before `await agoraEngine.enableVideo();`:
```dart
// Extensions marketplace hosts both third-party extensions as well as those developed by Agora. To use an Agora extensions, you do not need to call addExtension or enableExtension.
agoraEngine.enableExtension(
provider: "",
extension: "",
type: MediaSourceType.unknownMediaSource,
enable: true
);
```
If the extension uses the second camera to capture video, set the type parameter to `secondaryCameraSource`.
To enable multiple extensions, call `enableExtension` for each extension. The calls to `enableExtension` determines the order of each extension in the transmission pipeline. For example, if you enable extension A before extension B, extension A processes data from Video SDK before extension B.
3. **Set extension properties**
To customize the extension for your particular app, set suitable values for the extension properties. Refer to the extension documentation for a list of available property names and allowable values. To set a property, add the following code to the `setupVideoSDKEngine` method after `agoraEngine.enableExtension`:
```dart
agoraEngine.setExtensionProperty(
provider: "",
extension: "",
key: "",
value: "");
```
## Test your implementation [#test-your-implementation-6]
To ensure that you have integrated the extension in your app:
1. Set the variables and properties in the framework code to values suitable for your chosen extension.
2. In your IDE, open `/lib/main.dart` and update `appId`, `channelName` and `token` with the values from Agora Console.
3. Connect a test device to your development device.
4. In your IDE, click **Run app** or execute the `flutter run` command in the terminal. A moment later, you see the project installed on your device.
If this is the first time you run the app, grant camera and microphone permissions.
5. Join the same channel from another test device or the [Agora web demo](https://webdemo.agora.io/basicVideoCall/index.html).
6. Experience the features of your new extension.
## Reference [#reference-6]
This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.
### API reference [#api-reference-4]
* [`enableExtension`](https://api-ref.agora.io/en/video-sdk/flutter/6.x/API/class_irtcengine.html#api_irtcengine_enableextension)
* [`loadExtensionProvider`](https://api-ref.agora.io/en/video-sdk/flutter/6.x/API/class_irtcengine.html#api_irtcengine_loadextensionprovider)
* [`setExtensionProperty`](https://api-ref.agora.io/en/video-sdk/flutter/6.x/API/class_irtcengine.html#api_irtcengine_setextensionproperty)
* [`onExtensionStarted`](https://api-ref.agora.io/en/video-sdk/flutter/6.x/API/class_irtcengineeventhandler.html#callback_irtcengineeventhandler_onextensionstarted)
* [`onExtensionStopped`](https://api-ref.agora.io/en/video-sdk/flutter/6.x/API/class_irtcengineeventhandler.html#callback_irtcengineeventhandler_onextensionstopped)
* [`onExtensionError`](https://api-ref.agora.io/en/video-sdk/flutter/6.x/API/class_irtcengineeventhandler.html#callback_irtcengineeventhandler_onextensionerror)
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="react-native">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="react-native" />
Extensions are add-ons designed to rapidly extend the functionality of your app. [Extensions Marketplace](https://www.agora.io/en/agora-extensions-marketplace/) is home to extensions that make your app more fun. Extensions provide features such as Audio effects and voice changing, Face filters and background removal, and Live transcription and captioning.
In the Agora Extensions Marketplace:
* Vendors create and publish extensions to provide functionality such as audio and video processing.
* App developers use extensions to quickly implement fun and interactive functionality.
This page shows you how to integrate an extension from Agora Extensions Marketplace into your app. There can be specific guidance for each extension.
## Understand the tech [#understand-the-tech-7]
An extension accesses voice and video data when it is captured from the user's local device, modifies it, then plays the updated data to local and remote video channels.
**Extension call workflow**

A typical transmission pipeline consists of a chain of procedures, including capture, pre-processing, encoding, transmitting, decoding, post-processing, and play. Audio or video extensions are inserted into either the pre-processing or post-processing procedure, in order to modify the voice or video data in the transmission pipeline.
## Prerequisites [#prerequisites-7]
To test the code used in this page you need to have:
* An Agora [account](/en/realtime-media/marketplace/manage-agora-account) and [project](/en/realtime-media/marketplace/manage-agora-account).
* A computer with Internet access.
Ensure that no firewall is blocking your network communication.
* Implemented the [SDK quickstart](/en/realtime-media/video/quickstart).
- React Native 0.60 or later. For more information, see [Setting up the development environment](https://reactnative.dev/docs/environment-setup).
- Node 10 or later
- For iOS
* A machine running macOS
* Xcode 10 or later
* CocoaPods
* A physical or virtual mobile device running iOS 9.0 or later. If you use React Native 0.63 or later, ensure your iOS version is 10.0 or later.
- For Android
* A machine running macOS, Windows, or Linux
* [Java Development Kit (JDK) 11](https://openjdk.org/projects/jdk/11/) or later
* Android Studio
* A physical or virtual mobile device running Android 5.0 or later
## Project setup [#project-setup-7]
In order to integrate an extension into your project:
1. **Activate an extension**
2. Log in to [Agora Console](https://console.agora.io/v2).
3. In the left navigation panel, click **Extension Marketplace**, then click the extension you want to activate.
You are now on the extension detail page.
3. Select a pricing plan and click **Buy and Activate**.
* If you have already created an Agora project:
The **Projects** section appears and lists all of your projects.
* If you have not created an Agora project:
[Create a new project](/en/realtime-media/marketplace/manage-agora-account), the project appears in the **Projects** section.
4. Under **Projects** on the extension detail page, find the project in which you want to use the extension, then turn on the switch in the **Action** column.
5. **Get the apiKey and apiSecret for the extension**
If required for the extension, to get the extension apiKey and apiSecret, in the **Projects** extension detail page, click **View** in the **Secret** column.
3. Open your React-native project
Load the [SDK quickstart](/en/realtime-media/video/quickstart) Video Calling project you created previously.
4. Get the extension
Visit the [Agora Extensions Marketplace](https://www.agora.io/en/agora-extensions-marketplace/) and follow the procedure to download and install the desired extension.
You are now ready to integrate the extension in your app.
## Integrate the extension into your project [#integrate-the-extension-into-your-project-7]
This section presents the framework code you add to your React Native project to integrate an extension.
### Implement the user interface [#implement-the-user-interface]
You can use a `switch` to enable and disable the extension you wish to integrate. To do this, follow these steps:
1. **Import the necessary modules**
Add the following import from `react-native` before `Text,`:
```ts
Switch,
```
2. **Render the switch**
Add the following code in the return statement, after `Leave `:
```ts
{
if (enable_extension) {
disableExtension();
setEnableExtension(false);
} else {
enableExtension();
setEnableExtension(true);
}
}}
/>
```
### Integrate the extension [#integrate-the-extension]
1. **Declarations for the extension**
You can create constants for the extension provider and extension name that you need to integrate.
```ts
const extprovider = 'agora_segmentation';
const extension = 'PortraitSegmentation';
```
You also need a state variable to keep track of the status of your extension. Inside `App`, add the following code along with the other declarations:
```ts
const [enable_extension, setEnableExtension] = useState(false);
```
2. **Enable the extension**
You call `enableExtension` to enable your extension. To do this using the `switch`, add the following code after the `leave` function:
```ts
const enableExtension = () => {
if (Platform.OS === 'android') {
agoraEngineRef.current?.loadExtensionProvider(`${extprovider}_extension`);
}
agoraEngineRef.current?.enableExtension(extprovider, extension, true);
setEnableExtension(true);
};
```
3. **Disable the extension**
You call `disableExtension` to disable your extension. To do this using the `switch`, add the following code after the `enableExtension` function:
```ts
const disableExtension = () => {
agoraEngineRef.current?.enableExtension(extprovider, extension, false);
setEnableExtension(false);
};
```
4. **Add the necessary callbacks**
To get notified of important events, add the following callbacks to `agoraEngine.registerEventHandler({`:
```ts
onExtensionErrored: (
provider: string,
extName: string,
error: number,
msg: string,
) => {
console.log(
'onExtensionErrored',
'provider',
provider,
'extName',
extName,
'error',
error,
'msg',
msg,
);
},
onExtensionEvent: (
provider: string,
extName: string,
key: string,
value: string,
) => {
console.log(
'onExtensionEvent',
'provider',
provider,
'extName',
extName,
'key',
key,
'value',
value,
);
},
onExtensionStarted: (provider: string, extName: string) => {
console.log(
'onExtensionStarted',
'provider',
provider,
'extName',
extName,
);
setEnableExtension(true);
},
onExtensionStopped: (provider: string, extName: string) => {
console.log(
'onExtensionStopped',
'provider',
provider,
'extName',
extName,
);
setEnableExtension(false);
},
```
## Test your implementation [#test-your-implementation-7]
To ensure that you have integrated the extension in your app:
1. Run your app:
* *Android*
1. Enable Developer options on your Android device, and then connect it to your computer using a USB cable.
2. Run `npx react-native run-android` in the project root directory.
* *iOS:*
1. Run your app with Xcode.
If this is the first time you run the project, you need to grant microphone and camera access to your app.
2. Join the same channel from another test device or the [Agora web demo](https://webdemo.agora.io/basicVideoCall/index.html).
3. Experience the features of your new extension.
## Reference [#reference-7]
This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="unity">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="unity" />
Extensions are add-ons designed to rapidly extend the functionality of your game. [Extensions Marketplace](https://www.agora.io/en/agora-extensions-marketplace/) is home to extensions that make your app more fun. Extensions provide features such as Audio effects and voice changing, Face filters and background removal, and Live transcription and captioning.
In the Agora Extensions Marketplace:
* Vendors create and publish extensions to provide functionality such as audio and video processing.
* App developers use extensions to quickly implement fun and interactive functionality.
This page shows you how to integrate an extension from Agora Extensions Marketplace into your game. There can be specific guidance for each extension.
## Understand the tech [#understand-the-tech-8]
An extension accesses voice and video data when it is captured from the user's local device, modifies it, then plays the updated data to local and remote video channels.
**Extension call workflow**

A typical transmission pipeline consists of a chain of procedures, including capture, pre-processing, encoding, transmitting, decoding, post-processing, and play. Audio or video extensions are inserted into either the pre-processing or post-processing procedure, in order to modify the voice or video data in the transmission pipeline.
## Prerequisites [#prerequisites-8]
To test the code used in this page you need to have:
* An Agora [account](/en/realtime-media/marketplace/manage-agora-account) and [project](/en/realtime-media/marketplace/manage-agora-account).
* A computer with Internet access.
Ensure that no firewall is blocking your network communication.
* Implemented the [SDK quickstart](/en/realtime-media/video/quickstart)
- [Unity Hub](https://unity.com/download)
- [Unity Editor 2017.X LTS or higher](https://unity.com/releases/editor/archive)
- Microsoft Visual Studio 2017 or higher
## Project setup [#project-setup-8]
In order to integrate an extension into your project:
1. **Activate an extension**
2. Log in to [Agora Console](https://console.agora.io/v2).
3. In the left navigation panel, click **Extension Marketplace**, then click the extension you want to activate.
You are now on the extension detail page.
3. Select a pricing plan and click **Buy and Activate**.
* If you have already created an Agora project:
The **Projects** section appears and lists all of your projects.
* If you have not created an Agora project:
[Create a new project](/en/realtime-media/marketplace/manage-agora-account), the project appears in the **Projects** section.
4. Under **Projects** on the extension detail page, find the project in which you want to use the extension, then turn on the switch in the **Action** column.
5. **Get the apiKey and apiSecret for the extension**
If required for the extension, to get the extension apiKey and apiSecret, in the **Projects** extension detail page, click **View** in the **Secret** column.
3. Open your Unity project
Load the [SDK quickstart](/en/realtime-media/video/quickstart) Video Calling project you created previously.
4. Get the extension
Visit the [Agora Extensions Marketplace](https://www.agora.io/en/agora-extensions-marketplace/) and follow the procedure to download and install the desired extension.
You are now ready to integrate the extension in your game.
## Integrate the extension into your project [#integrate-the-extension-into-your-project-8]
This section presents the framework code you add to your Unity project to integrate an extension.
1. **Load the extension provider**
You call `loadExtensionProvider` during Agora Engine initialization to specify the extension library path. To do this, add the following code to the `SetupVideoSDKEngine` method after you initialize the engine with `RtcEngine.Initialize`:
```csharp
RtcEngine.LoadExtensionProvider("");
```
2. **Enable the extension**
To enable the extension, add the following code to the `Join` method before `RtcEngine.EnableVideo();`:
```csharp
RtcEngine.EnableExtension(
provider: "",
extension: "",
enable: true,
type: MEDIA_SOURCE_TYPE.UNKNOWN_MEDIA_SOURCE
);
```
Extensions marketplace hosts both third-party extensions as well as those developed by Agora. To use an Agora extension, you do not need to call EnableExtension.
To enable multiple extensions, call `EnableExtension` for each extension. The calls to `EnableExtension` determines the order of each extension in the transmission pipeline. For example, if you enable extension A before extension B, Video SDK processes data from extension A before extension B.
3. **Set extension properties**
To customize the extension for your particular game, set suitable values for the extension properties. Refer to the extension documentation for a list of available property names and allowable values. To set a property, add the following code to the `Join` method after `RtcEngine.EnableExtension`:
```csharp
RtcEngine.SetExtensionProperty(
provider: "",
extension: "",
key: "",
value: ""
);
```
## Test your implementation [#test-your-implementation-8]
To ensure that you have integrated the extension in your game:
1. Set the variables and properties in the project code to values suitable for your chosen extension.
2. Generate a temporary token in Agora Console.
3. In your browser, navigate to the [Agora web demo](https://webdemo.agora.io/basicVideoCall/index.html) and update *App ID*, *Channel*, and *Token* with the values for your temporary token, then click **Join**.
4. In **Unity Editor**, in `Assets/Agora-RTC-Plugin/Agora-Unity-RTC-SDK/Code/JoinChannelVideo.cs`, update `_appID`, `_channelName`, and `_token` with the values for your temporary token.
5. In **Unity Editor**, click **Play**. A moment later you see the game installed on your device.
6. Experience the features of your new extension.
## Reference [#reference-8]
This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.
### API reference [#api-reference-5]
* [`EnableExtension`](https://api-ref.agora.io/en/video-sdk/unity/4.x/API/class_irtcengine.html#api_irtcengine_enableextension)
* [`LoadExtensionProvider`](https://api-ref.agora.io/en/video-sdk/unity/4.x/API/class_irtcengine.html#api_irtcengine_loadextensionprovider)
* [`SetExtensionProperty`](https://api-ref.agora.io/en/video-sdk/unity/4.x/API/class_irtcengine.html#api_irtcengine_setextensionproperty)
<_PlatformProcessedMarker close="true" />
# Media Pull overview (/en/realtime-media/media-pull)
Agora's Media Pull service enables the injection of external media streams into real-time Agora channels. Hosts and audiences can seamlessly view and hear the additional streams while interacting in real-time, enhancing engagement and content versatility. The service is designed to work with Agora's Voice Calling, Video Calling, and Interactive Live Streaming.
With support for multiple streaming protocols, extensive media formats, and ultra-low latency, Media Pull ensures smooth and flexible media injection. Control playback timing for injected streams to synchronize content seamlessly within your channel, creating dynamic and interactive experiences.
## Quick links [#quick-links]
## Key features [#key-features]
Inject an online media stream into the Agora channel as a live video source using the RESTful API.
After the injection succeeds, the online media stream automatically plays in the Agora channel, and remote users can see the content of the media stream.
The audio or video stream is injected with ultra-low latency, allowing the audience to see and hear the stream in real time.
Hosts can control when to start playing the injected media stream to coordinate timed media stream playback.
Supports video codec formats H.264, H.265, and VP9; audio codec formats: AAC and OPUS; and container formats FLV, MP4, MPEG-TS, Matroska (MKV), and HLS.
Support for HTTPS, and RTMP streaming protocols.
# Media Push overview (/en/realtime-media/media-push)
Agora's Media Push service streams audio and video from Agora channels to CDNs and other RTMP-based platforms. With advanced edge transcoding, it minimizes latency and provides full control over the layout and appearance of the stream. This enables seamless distribution to multiple destinations, combining real-time engagement with broad audience reach.
Extend the reach of your Agora streams to third-party services. Whether it’s live events, webinars, or hybrid broadcasts, Media Push amplifies your content while preserving the low-latency, high-quality experience that Agora is known for.
## Quick links [#quick-links]
## Key features [#key-features]
Ideal for hybrid use cases where a small audience group can interact with the host(s) and each other, while most of the audience only listens/watches.
Media Push works with major CDN providers across the globe, with support for RTMP and RTMPS protocols.
Agora’s RESTful API makes integration easy while providing flexibility to customers.
Uploading live interactive content to CDN enables customers to expand their audience for use cases where only a section of a large audience needs live engagement.
Upload to CDN works for any use case, and is particularly useful for verticals like virtual events, social live streaming, education webinars and more.
# Pricing (/en/realtime-media/on-premise-recording/billing)
This page summarizes the pricing model for On-Premise Recording based on the legacy source document.
Agora bills On-Premise Recording by recorded usage time. Audio and video are charged separately, and video pricing depends on aggregate recorded resolution. If a recording instance captures both audio and video at the same time, Agora charges video minutes for that interval.
## Key points [#key-points]
* Billing is calculated monthly for projects under your Agora account.
* Audio-only recording is billed by audio minutes.
* Video recording is billed by video minutes and resolution tier.
* Idle time during a recording session is billed at the audio rate.
## Related pages [#related-pages]
* [Billing policies](./reference/pricing.mdx)
* [Manage your Agora account](./manage-agora-account.mdx)
* [On-Premise Recording API reference](/en/api-reference/api-ref/on-premise-recording)
# On-Premise Recording overview (/en/realtime-media/on-premise-recording)
The Agora On-Premise Recording SDK is a recording component developed by Agora for audio and video calls and live broadcasts. Through simple operation methods, it helps developers quickly and flexibly deploy recording services to record audio and video calls or live broadcasts and generate recording files in MP4 format.
With the recording function, you can save the content of voice calls, video calls, and live broadcasts, and make it available for later viewing. For example, learners can review course recordings at their convenience or catch up on lessons they missed after attending live online classes.
The SDK works with Video SDK and is compatible with the Agora RTC Native and framework SDKs (version 1.7.0 or higher) and the Agora RTC Web SDK (version 1.12.0 or higher).
The Agora On-Premise Recording SDK requires deployment on Linux servers with self-managed maintenance. If you prefer not to deploy Linux servers and want to implement recording through RESTful APIs, use [Agora Cloud Recording](/en/realtime-media/cloud-recording).
## Start building [#start-building]
## Features [#features]
Choose between single-stream recording (separate MP4 files for each UID) or converged recording (combined audio/video in one MP4 file) to meet different use cases.
Record audio only, video only, or both simultaneously. Compatible with Agora RTC Native/Framework SDK (v1.7.0+) and Web SDK (v1.12.0+).
Set custom mixed-stream layouts, specify user screen sizes and positions on video canvas, and configure background images in converged recording mode.
Record specific UIDs in channels and add text watermarks, dynamic timestamp watermarks, or static image watermarks to recorded video files.
Deploy on Ubuntu 18.04+ and CentOS 7.0+ with cluster support, proxy server configuration, cloud proxy services, and end-to-end security for encrypted channels.
Capture JPG format screenshots from live streams and save them to specified local paths. Access raw video data in YUV, JPG, and encoded video frame formats.
# Agora account management (/en/realtime-media/on-premise-recording/manage-agora-account)
This page shows you how to sign up for an Agora account, create a new project, and get the app ID and app certificate to generate a temporary token.
## Get started with Agora [#get-started-with-agora]
To join an On-Premise Recording session, you need an Agora App ID. This section shows you how to set up an Agora account, create an Agora project and get the required information from [Agora Console](https://console.agora.io/v2).
### Sign up for an Agora account [#sign-up-for-an-agora-account]
To use Agora products and services, create an Agora account with your email, phone number, or a third-party account.
Sign up with email
Sign up with a third-party account
1. Go to the [signup page](https://sso.agora.io/en/signup).
2. Fill in the required fields.
3. Carefully read the **Terms of Service**, **Privacy Policy**, and **Acceptable Use Policy**, and tick the checkbox.
4. Click **Continue**.
5. Enter your **verification code** and click **Confirm**.
6. Follow the on-screen instructions to provide your name, company name, and phone number, set a password, and click **Continue**.
1. On the [Agora Console](https://console.agora.io/v2) login page, select the third-party account you want to use.
2. Follow the on-screen instructions to complete verification.
3. Click **Create a new account**.
4. Carefully read the **Terms of Service**, **Privacy Policy**, and **Acceptable Use Policy**, and select the checkbox.
5. Click **Continue**.
Once you sign up successfully, your account is automatically logged in. Follow the on-screen instructions to create your first project and test out real-time communications.
For later visits, log in to [Agora Console](https://console.agora.io/v2) with your phone number, email address, or linked third-party account.
### Create an Agora project [#create-an-agora-project]
To create an Agora project, do the following:
1. In [Agora Console](https://console.agora.io/v2), open the [Projects](https://console.agora.io/v2/project-management) page.
2. Click **Create New**.
3. Follow the on-screen instructions to enter a project name and use case, and check **Secured mode: APP ID + Token (Recommended)** as the authentication mechanism.

4. Click **Submit**. You see the new project on the **Projects** page.
### Get the App ID [#get-the-app-id]
Agora automatically assigns a unique identifier to each project, called an App ID.
To copy this App ID, find your project on the [Projects](https://console.agora.io/v2/project-management) page in Agora Console, and click the copy icon in the **App ID** column.

## Security and authentication [#security-and-authentication]
Use the following features from your Agora account to implement security and authentication features in your apps.
### Get the App Certificate [#get-the-app-certificate]
When generating an authentication token on your app server, you need an App Certificate, in addition to the App ID.
To get an App Certificate, do the following:
1. On the [Projects](https://console.agora.io/v2/project-management) page, click the pencil icon to edit the project you want to use.

2. Click the copy icon under **Primary Certificate**.

### Generate temporary tokens [#generate-temporary-tokens]
To ensure communication security, best practice is to use tokens to authenticate the users who log in from your app.
To generate a temporary RTC token for use in your Video SDK projects:
1. On the [Projects](https://console.agora.io/v2/project-management) page, click the pencil icon next to your project.
2. On the **Security** panel, click **Generate Temp Token**, enter a channel name in the pop-up box and click **Generate**. Copy the generated RTC token for use in your On-Premise Recording projects.
To generate a token for other Agora products:
1. In your browser, navigate to the [Agora token builder](https://agora-token-generator-demo.vercel.app/).
2. Choose the Agora product your user wants to log in to. Fill in **App ID** and **App Certificate** with the
details of your project in Agora Console.
3. Customize the token for each user. The required fields are visible in the Agora token builder.
4. Click **Generate Token**.
The token appears in Token Builder.
5. Copy the token and use it in your app.
For more information on managing other aspects of your Agora account, see [Agora console overview](/en/realtime-media/video/reference/console-overview).
# Agora MCP (/en/realtime-media/on-premise-recording/mcp)
The Agora MCP server gives your AI assistant direct access to Agora documentation, so it can look up APIs, SDK methods, and platform details in real time.
The server is available at:
```text
https://mcp.agora.io
```
## Installation [#installation]
```bash
codex mcp add --url https://mcp.agora.io agora-docs
```
# Quickstart (/en/realtime-media/on-premise-recording/quickstart)
<_PlatformTabsGroup groupMode="structured" canonicalPlatform="linux-cpp" platforms="["linux-cpp","linux-java"]" showTabs="true">
<_PlatformPanel platform="linux-cpp">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="linux-cpp" platform="linux-cpp" />
This article describes how to use the On-Premise Recording SDK to record real-time audio and video.
## Understand the tech [#understand-the-tech]
Recording audio and video in a channel using the On-Premise Recording SDK works by adding a special user to the Video SDK channel. This special user captures the audio and video from the channel, transcodes it, and stores it on your Linux server.

Integrate the On-Premise Recording SDK into your Linux server, not your client app. To record audio and video without deploying a Linux server, use Agora [Cloud Recording](/en/realtime-media/cloud-recording).
## Prerequisites [#prerequisites]
Before you begin, complete the following steps and ensure your environment meets the required specifications:
* [Create an Agora project](/en/realtime-media/on-premise-recording/manage-agora-account#generate-temporary-token) in the Agora Console and obtain an App ID and a temporary token.
* Implement a Video SDK project that includes basic audio and video interaction.
**Server hardware requirements:**
* **CPU**: 8 cores, 1.8 GHz
* **Memory**: At least 4 GB (recommended)
**Server software requirements:**
* **Operating system**: Ubuntu 18.04 or later, or CentOS 7.0 or later
* **CPU architecture**: arm64 or x86-64
* **glibc**: version 2.18 or later
* **gcc**: version 4.8 or later
**Network requirements:**
* The server is connected to the public internet and has a public IP address.
* The server allows access to the following domains:
* `*.agora.io`
* `*.agoralab.co`
## Project setup [#project-setup]
This section shows how to integrate the On-Premise Recording SDK into your app.
### Integrate the SDK [#integrate-the-sdk]
1. Download the latest On-Premise Recording SDK package from the [SDKs](/en/api-reference/sdks?product=on-premise-recording\&platform=linux) page.
2. Extract the package. The directory structure should look like this:
```text
.
├── agora_sdk
│ ├── include
│ ├── libagora-fdkaac.so
│ ├── libagora_rtc_sdk.so
│ └── libaosl.so
└── example
├── CMakeLists.txt
├── build.sh
├── out
├── recorder
├── recorder.json
├── singleVideo.json
├── scripts
└── third-party
```
3. Integrate the SDK into your project:
* Import the header files from `agora_sdk/include` into your project.
* Link the following dynamic library files from the `agora_sdk` directory:
* `libagora-fdkaac.so`
* `libagora_rtc_sdk.so`
* `libaosl.so`
The `example` directory contains a complete sample project for local recording. To run it, see [Test the project](#test-the-project).
## Implement On-Premise Recording [#implement-on-premise-recording]
This section shows how to implement On-Premise recording in your app, step by step.
The following figure illustrates the essential steps:
Quickstart sequence diagram

### Initialize the service [#initialize-the-service]
Create the Agora service using `createAgoraService`. Then initialize it with media configuration options and logging settings.
```cpp
auto service = createAgoraService();
agora::base::AgoraServiceConfiguration service_config;
service_config.enableAudioDevice = false;
service_config.enableAudioProcessor = true;
service_config.enableVideo = true;
service_config.appId = config.appId.c_str();
service_config.useStringUid = config.UseStringUid;
service->initialize(service_config);
service->setLogFile("./io.agora.rtc_sdk/agorasdk.log", 1024 * 1024 * 5);
```
### Create the recorder instance [#create-the-recorder-instance]
Call `createAgoraMediaComponentFactory` and use it to create a `IAgoraMediaRtcRecorder` instance. Then initialize the recorder with the service and recording mode.
```cpp
agora::rtc::IMediaComponentFactory* factory = createAgoraMediaComponentFactory();
agora::agora_refptr recorder = factory->createMediaRtcRecorder();
// Set the recording mode
// - false: Record each user's audio and video stream separately (single stream recording)
// - true: Record all users' audio and video streams together (composite recording)
bool isMix = false;
// Initialize recorder
recorder->initialize(service, isMix);
```
### Register the event handler [#register-the-event-handler]
Call `registerRecorderEventHandle` to register a user-defined event handler that receives recorder callbacks.
```cpp
std::unique_ptr eventHandler{new RecorderEventHandler(recorder, config)};
recorder->registerRecorderEventHandle(eventHandler.get());
```
### Subscribe to audio and video streams [#subscribe-to-audio-and-video-streams]
Call `subscribeAllAudio` and `subscribeAllVideo` to receive all audio and video streams in the channel.
```cpp
recorder->subscribeAllAudio();
recorder->subscribeAllVideo(options);
```
### Configure recording [#configure-recording]
Call `setRecorderConfig` to define recording parameters such as resolution, frame rate, audio settings, and the storage location.
Ensure that the directory specified in `storagePath` exists. If not, the recording will fail.
```cpp
// Set recording configuration
agora::media::MediaRecorderConfiguration recorder_config;
recorder_config.width = config.video.width;
recorder_config.height = config.video.height;
recorder_config.fps = config.video.fps;
recorder_config.storagePath = config.recorderPath.c_str();
recorder_config.sample_rate = config.audio.sampleRate;
recorder_config.channel_num = config.audio.numOfChannels;
// Set recording stream type: audio stream, video stream, or audio and video stream
recorder_config.streamType = static_cast(config.recorderStreamType);
// Set the maximum recording duration
recorder_config.maxDurationMs = config.maxDuration * 1000;
recorder->setRecorderConfig(recorder_config);
```
### Join the channel and start recording [#join-the-channel-and-start-recording]
Call `joinChannel` to join the specified channel and `startRecording` to begin recording.
```cpp
recorder->joinChannel(config.token.c_str(), config.ChannelName.c_str(), config.UserId.c_str());
recorder->startRecording();
```
### Stop recording and release resources [#stop-recording-and-release-resources]
Call the cleanup methods to end recording, unsubscribe from streams, and release memory.
```cpp
recorder->unsubscribeAllAudio();
recorder->unsubscribeAllVideo();
recorder->stopRecording();
recorder->unregisterRecorderEventHandle(eventHandler.get());
eventHandler = nullptr;
recorder->leaveChannel();
recorder = nullptr;
service->release();
```
### Complete sample code [#complete-sample-code]
The following example shows the full implementation of an On-Premise Recording workflow.
Complete sample code for On-Premise Recording
```cpp
// Create and initialize the Agora service, and configure logging
auto service = createAgoraService();
agora::base::AgoraServiceConfiguration service_config;
service_config.enableAudioDevice = false;
service_config.enableAudioProcessor = true;
service_config.enableVideo = true;
service_config.appId = config.appId.c_str();
service_config.useStringUid = config.UseStringUid;
service->initialize(service_config);
service->setLogFile("./io.agora.rtc_sdk/agorasdk.log", 1024 * 1024 * 5);
// Create the media recorder instance
agora::rtc::IMediaComponentFactory* factory = createAgoraMediaComponentFactory();
agora::agora_refptr recorder = factory->createMediaRtcRecorder();
// Initialize the recorder
// Set to true for mixed recording or false for individual streams
bool isMix = false;
recorder->initialize(service, isMix);
// Register the event handler for recorder callbacks
std::unique_ptr eventHandler{new RecorderEventHandler(recorder, config)};
recorder->registerRecorderEventHandle(eventHandler.get());
// Subscribe to all audio and video streams
recorder->subscribeAllAudio();
recorder->subscribeAllVideo(options); // 'options' should be properly defined before use
// Configure recorder settings
agora::media::MediaRecorderConfiguration recorder_config;
recorder_config.width = config.video.width;
recorder_config.height = config.video.height;
recorder_config.fps = config.video.fps;
recorder_config.storagePath = config.recorderPath.c_str();
recorder_config.sample_rate = config.audio.sampleRate;
recorder_config.channel_num = config.audio.numOfChannels;
recorder_config.streamType = static_cast(config.recorderStreamType);
recorder_config.maxDurationMs = config.maxDuration * 1000;
recorder->setRecorderConfig(recorder_config);
// Join the channel and start recording
recorder->joinChannel(config.token.c_str(), config.ChannelName.c_str(), config.UserId.c_str());
recorder->startRecording();
// Stop recording and clean up
recorder->unsubscribeAllAudio();
recorder->unsubscribeAllVideo();
recorder->stopRecording();
recorder->unregisterRecorderEventHandle(eventHandler.get());
eventHandler = nullptr;
recorder->leaveChannel();
recorder = nullptr;
service->release();
```
## Test the project [#test-the-project]
Agora provides a complete sample project in the SDK download package. After downloading and extracting the package, follow these steps to build and run it:
1. Navigate to the sample project directory:
```sh
cd agora_rtc_sdk/example
```
2. Build the sample project:
```sh
./build.sh
```
3. Add the SDK library path to the `LD_LIBRARY_PATH` environment variable:
```sh
export LD_LIBRARY_PATH=../../agora_sdk:$LD_LIBRARY_PATH
```
4. Choose a configuration file for your recording mode:
* Use `recorder.json` for composite recording.
* Use `singleVideo.json` for individual recording.
For more information on the differences between mixed and individual recording modes, see [Individual recording](/en/realtime-media/on-premise-recording/build/record-audio-and-video/individual-mode) and [Composite recording](/en/realtime-media/on-premise-recording/build/record-audio-and-video/composite-mode).
5. Run the sample recorder with the selected configuration file:
```sh
# Example: Run individual recording
./out/sample_recorder singleVideo.json
```
## Reference [#reference]
This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.
### API reference [#api-reference]
* [`createAgoraService`](/en/api-reference/api-ref/on-premise-recording#createagoraservice)
* [`initialize`](/en/api-reference/api-ref/on-premise-recording#initialize)
* [`setLogFile`](/en/api-reference/api-ref/on-premise-recording#setlogfile)
* [`createAgoraMediaComponentFactory`](/en/api-reference/api-ref/on-premise-recording#createagoramediacomponentfactory)
* [`registerRecorderEventHandle`](/en/api-reference/api-ref/on-premise-recording#registerrecordereventhandle)
* [`subscribeAllAudio`](/en/api-reference/api-ref/on-premise-recording#subscribeallaudio)
* [`subscribeAllVideo`](/en/api-reference/api-ref/on-premise-recording#subscribeallvideo)
* [`setRecorderConfig`](/en/api-reference/api-ref/on-premise-recording#setrecorderconfig)
* [`joinChannel`](/en/api-reference/api-ref/on-premise-recording#joinchannel)
* [`startRecording`](/en/api-reference/api-ref/on-premise-recording#startrecording)
* [`unsubscribeAllAudio`](/en/api-reference/api-ref/on-premise-recording#unsubscribeallaudio)
* [`unsubscribeAllVideo`](/en/api-reference/api-ref/on-premise-recording#unsubscribeallvideo)
* [`stopRecording`](/en/api-reference/api-ref/on-premise-recording#stoprecording)
* [`unregisterRecorderEventHandle`](/en/api-reference/api-ref/on-premise-recording#unregisterrecordereventhandle)
* [`leaveChannel`](/en/api-reference/api-ref/on-premise-recording#leavechannel)
* [`release`](/en/api-reference/api-ref/on-premise-recording#release)
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="linux-java">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="linux-cpp" platform="linux-java" />
This article describes how to use the On-Premise Recording SDK to record real-time audio and video.
## Understand the tech [#understand-the-tech-1]
Recording audio and video in a channel using the On-Premise Recording SDK works by adding a special user to the Video SDK channel. This special user captures the audio and video from the channel, transcodes it, and stores it on your Linux server.

Integrate the On-Premise Recording SDK into your Linux server, not your client app. To record audio and video without deploying a Linux server, use Agora [Cloud Recording](/en/realtime-media/cloud-recording).
## Prerequisites [#prerequisites-1]
Before you begin, complete the following steps and ensure your environment meets the required specifications:
* [Create an Agora project](/en/realtime-media/on-premise-recording/manage-agora-account#generate-temporary-token) in the Agora Console and obtain an App ID and a temporary token.
* Implement a Video SDK project that includes basic audio and video interaction.
**Server hardware requirements:**
* **CPU**: 8 cores, 1.8 GHz
* **Memory**: At least 4 GB (recommended)
**Server software requirements:**
* **Operating system**: Ubuntu 18.04 or later, or CentOS 7.0 or later
* **CPU architecture**: arm64 or x86-64
* **glibc**: version 2.18 or later
* **gcc**: version 4.8 or later
**Network requirements:**
* The server is connected to the public internet and has a public IP address.
* The server allows access to the following domains:
* `*.agora.io`
* `*.agoralab.co`
## Project setup [#project-setup-1]
This section shows how to integrate the On-Premise Recording SDK into your app.
Add using Maven
Add manually
Add the following dependencies to your project’s` pom.xml` file based on your platform:
* **For x86\_64**:
```xml
io.agora.rtclinux-recording-java-sdk4.4.150.5
```
* **For arm64**:
```xml
io.agora.rtclinux-recording-java-sdk4.4.150.5-aarch64
```
### Download and unzip [#download-and-unzip]
1. Download the latest On-Premise Recording SDK package from the [SDKs](/en/api-reference/sdks?product=on-premise-recording\&platform=linux) page.
2. Unzip the SDK and you see the following contents:
```txt
doc/ JavaDoc documentation
examples/ Sample projects and code
sdk/ Core SDK files
sdk/agora-recording-sdk.jar Java class library
sdk/agora-recording-sdk-javadoc.jar JavaDoc (optional, for IDE support)
```
### Integrate the SDK [#integrate-the-sdk-1]
Choose one of the following methods to add the SDK to your project.
#### Use a local maven repository [#use-a-local-maven-repository]
1. Install the SDK JAR using Maven:
To install only the SDK JAR:
```bash
mvn install:install-file \\
-Dfile=sdk/agora-recording-sdk.jar \\
-DgroupId=io.agora.rtc \\
-DartifactId=linux-recording-java-sdk \\
-Dversion=4.4.150.5 \\
-Dpackaging=jar \\
-DgeneratePom=true
```
To install both the SDK JAR and JavaDoc:
```bash
mvn install:install-file \\
-Dfile=sdk/agora-recording-sdk.jar \\
-DgroupId=io.agora.rtc \\
-DartifactId=linux-recording-java-sdk \\
-Dversion=4.4.150.5 \\
-Dpackaging=jar \\
-DgeneratePom=true \\
-Djavadoc=sdk/agora-recording-sdk-javadoc.jar
```
1. Add the dependency to your `pom.xml`:
```xml
io.agora.rtclinux-recording-java-sdk4.4.150.5
```
#### Reference the JAR directly [#reference-the-jar-directly]
1. Create a `libs` directory and copy the JAR files:
```bash
mkdir -p libs
cp sdk/agora-recording-sdk.jar libs/
cp sdk/agora-recording-sdk-javadoc.jar libs/ # Optional, for IDE JavaDoc support
```
2. Add the JAR file to your classpath when compiling or running:
```xml
java -cp .:libs/agora-recording-sdk.jar YourMainClass
```
info
To enable JavaDoc support in your IDE (such as IntelliJ IDEA or Eclipse), attach `agora-recording-sdk-javadoc.jar` to the library reference.
### Load native libraries [#load-native-libraries]
The SDK depends on native `.so` libraries written in C++. Whether you integrate the SDK using Maven or manually, you must ensure the Java Virtual Machine (JVM) can locate and load these libraries at runtime.
#### Extract native `.so` files [#extract-native-so-files]
The native `.so` files are bundled inside the `agora-recording-sdk.jar` or `linux-recording-java-sdk-.jar` file. You need to extract them manually:
1. Create a directory to store the native library files:
```bash
mkdir -p libs
cd libs
```
2. Use the `jar` command to extract contents:
If you're using the local SDK:
```bash
jar xvf agora-recording-sdk.jar
```
If you're using the Maven-installed SDK:
```bash
jar xvf ~/.m2/repository/io/agora/rtc/linux-recording-java-sdk/4.4.150.5/linux-recording-java-sdk-4.4.150.5.jar
```
After extraction, you'll find a directory structure like this:
```txt
libs/
├── agora-recording-sdk.jar
├── native/
│ └── linux/
│ ├── x86_64/
│ │ ├── libagora_rtc_sdk.so
│ │ ├── libagora-fdkaac.so
│ │ ├── libaosl.so
│ │ └── librecording.so
│ └── aarch64/ (if available for arm64)
│ ├── libagora_rtc_sdk.so
│ ├── libagora-fdkaac.so
│ ├── libaosl.so
│ └── librecording.so
```
#### Configure the library load path [#configure-the-library-load-path]
The JVM must be configured to locate the extracted `.so` files. You can do this in one of two ways:
* **Option 1: Use `LD_LIBRARY_PATH` (Recommended)**
1. Set the environment variable before launching your app:
```bash
# Assuming the .so files are in ./libs/native/linux/x86_64
LIB_DIR=$(pwd)/libs/native/linux/x86_64
export LD_LIBRARY_PATH=$LIB_DIR:$LD_LIBRARY_PATH
```
2. Run your application:
```xml
java -jar your-app.jar
# Or with classpath:
java -cp "your-classpath" YourMainClass
```
* **Option 2: Use JVM option `-Djava.library.path`**
This method directly tells the JVM where to find the native libraries:
```xml
java -Djava.library.path=./libs/native/linux/x86_64 -jar your-app.jar
# Or with classpath:
java -Djava.library.path=./libs/native/linux/x86_64 -cp "your-classpath" YourMainClass
```
info
Using `LD_LIBRARY_PATH` is more reliable, especially when native libraries depend on each other.
#### Automate with a startup script (Optional) [#automate-with-a-startup-script-optional]
You can create a shell script to automate the setup:
```bash
#!/bin/bash
# Get the directory of this script
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Define paths
LIB_PATH="$SCRIPT_DIR/libs/native/linux/x86_64"
SDK_JAR="$SCRIPT_DIR/libs/agora-recording-sdk.jar"
MAIN_CLASS="your.main.Class"
APP_CP="your-other-classpaths"
# Set environment variable
export LD_LIBRARY_PATH=$LIB_PATH:$LD_LIBRARY_PATH
# Build full classpath
CLASSPATH=".:$SDK_JAR:$APP_CP"
# Run Java program
java -Djava.library.path=$LIB_PATH -cp "$CLASSPATH" $MAIN_CLASS
```
Make the script executable:
```bash
chmod +x run.sh
./run.sh
```
## Implement On-Premise Recording [#implement-on-premise-recording-1]
This section shows how to implement On-Premise recording in your app, step by step.
The following figure illustrates the essential steps:
Quickstart sequence diagram

### Initialize the service [#initialize-the-service-1]
Create an `AgoraService` instance and initialize it with your App ID and feature flags.
```java
// Create and configure the service
AgoraService agoraService = new AgoraService();
AgoraServiceConfiguration config = new AgoraServiceConfiguration();
config.setEnableAudioDevice(false);
config.setEnableAudioProcessor(true);
config.setEnableVideo(true);
config.setAppId(recorderConfig.getAppId());
config.setUseStringUid(recorderConfig.isUseStringUid());
// Set up log configuration
LogConfig logConfig = new LogConfig();
logConfig.setFileSizeInKB(1024 * 20);
logConfig.setFilePath("logs/agora_logs/agorasdk.log");
config.setLogConfig(logConfig);
// Initialize the Agora service
int ret = agoraService.initialize(config);
```
### Create a recorder instance [#create-a-recorder-instance]
Call `createMediaRtcRecorder` to create a recorder instance,and then use `AgoraMediaRtcRecorder.initialize` to initialize it.
```java
// Create recorder
AgoraMediaRtcRecorder agoraMediaRtcRecorder = agoraService.createMediaRtcRecorder();
// Initialize recorder
// enableMix sets the recording mode
// - false: Record each user’s audio and video stream separately (individual stream recording)
// - true: Record all users’ audio and video as a mixed stream (mixed recording)
boolean enableMix = false;
// service is the AgoraService object initialized in the previous step
agoraMediaRtcRecorder.initialize(agoraService, enableMix);
```
### Register event handlers [#register-event-handlers]
Register an event handler to receive recording callbacks.
```java
// Define the event handler
private IAgoraMediaRtcRecorderEventHandler eventHandler = new IAgoraMediaRtcRecorderEventHandler() {
@Override
public void onConnected(String channelId, String userId) {
// Handle connection event
}
@Override
public void onDisconnected(String channelId, String userId, Constants.ConnectionChangedReasonType reason) {
// Handle disconnection event
}
// Add more overrides as needed
};
// Register the event handler
agoraMediaRtcRecorder.registerRecorderEventHandler(eventHandler);
```
### Subscribe to audio and video streams [#subscribe-to-audio-and-video-streams-1]
Subscribe to all audio streams and high-quality video streams in the channel.
```java
// Subscribe to audio
agoraMediaRtcRecorder.subscribeAllAudio();
// Subscribe to video
VideoSubscriptionOptions options = new VideoSubscriptionOptions();
options.setEncodedFrameOnly(false);
options.setType(Constants.VideoStreamType.VIDEO_STREAM_HIGH);
agoraMediaRtcRecorder.subscribeAllVideo(options);
```
### Configure the recorder [#configure-the-recorder]
Set the resolution, frame rate, duration, and file path for the recording.
```java
// Configure the recorder
MediaRecorderConfiguration recorderConfig = new MediaRecorderConfiguration();
recorderConfig.setWidth(width != 0 ? width : recorderConfig.getVideo().getWidth());
recorderConfig.setHeight(height != 0 ? height : recorderConfig.getVideo().getHeight());
recorderConfig.setFps(recorderConfig.getVideo().getFps());
recorderConfig.setMaxDurationMs(recorderConfig.getMaxDuration() * 1000);
recorderConfig.setStoragePath(recorderConfig.getRecorderPath());
recorderConfig.setSampleRate(recorderConfig.getAudio().getSampleRate());
recorderConfig.setChannelNum(recorderConfig.getAudio().getNumOfChannels());
recorderConfig.setStreamType(Constants.MediaRecorderStreamType.STREAM_TYPE_BOTH);
recorderConfig.setVideoSourceType(Constants.VideoSourceType.VIDEO_SOURCE_CAMERA_SECONDARY);
agoraMediaRtcRecorder.setRecorderConfig(recorderConfig);
```
Ensure that the `storagePath` exists before you start recording. If the directory does not exist, the recording fails.
### Join the channel and start recording [#join-the-channel-and-start-recording-1]
Use the following code to join a channel and begin recording:
```java
agoraMediaRtcRecorder.joinChannel(
recorderConfig.getToken(),
recorderConfig.getChannelName(),
recorderConfig.getUserId()
);
agoraMediaRtcRecorder.startRecording();
```
### Stop and release resources [#stop-and-release-resources]
Unsubscribe from streams, stop the recording, and release resources.
```java
agoraMediaRtcRecorder.unsubscribeAllAudio();
agoraMediaRtcRecorder.unsubscribeAllVideo();
agoraMediaRtcRecorder.stopRecording();
agoraMediaRtcRecorder.unregisterRecorderEventHandler(eventHandler);
agoraMediaRtcRecorder.leaveChannel();
agoraMediaRtcRecorder.release();
agoraService.release();
```
The recorded MP4 file is saved to the storage path you specified in the recording configuration.
### Complete sample code [#complete-sample-code-1]
Complete sample code for On-Premise Recording
```java
// Initialize Service resources and set log
AgoraService agoraService = new AgoraService();
AgoraServiceConfiguration config = new AgoraServiceConfiguration();
config.setEnableAudioDevice(false);
config.setEnableAudioProcessor(true);
config.setEnableVideo(true);
config.setAppId(recorderConfig.getAppId());
config.setUseStringUid(recorderConfig.isUseStringUid());
LogConfig logConfig = new LogConfig();
logConfig.setFileSizeInKB(1024 * 20);
logConfig.setFilePath("logs/agora_logs/agorasdk.log");
config.setLogConfig(logConfig);
int ret = agoraService.initialize(config);
// Create recorder
AgoraMediaRtcRecorder agoraMediaRtcRecorder = agoraService.createMediaRtcRecorder();
// Initialize recorder
boolean enableMix = false;
agoraMediaRtcRecorder.initialize(agoraService, enableMix);
// Register callbacks
IAgoraMediaRtcRecorderEventHandler eventHandler = new IAgoraMediaRtcRecorderEventHandler() {
@Override
public void onConnected(String channelId, String userId) {
}
@Override
public void onDisconnected(String channelId, String userId, Constants.ConnectionChangedReasonType reason) {
}
// Other event callbacks
};
agoraMediaRtcRecorder.registerRecorderEventHandler(eventHandler);
// Subscribe to streams
agoraMediaRtcRecorder.subscribeAllAudio();
VideoSubscriptionOptions options = new VideoSubscriptionOptions();
options.setEncodedFrameOnly(false);
options.setType(Constants.VideoStreamType.VIDEO_STREAM_HIGH);
agoraMediaRtcRecorder.subscribeAllVideo(options);
// Set recording-related configurations
MediaRecorderConfiguration mediaRecorderConfiguration = new MediaRecorderConfiguration();
mediaRecorderConfiguration.setWidth(width != 0 ? width : recorderConfig.getVideo().getWidth());
mediaRecorderConfiguration.setHeight(height != 0 ? height : recorderConfig.getVideo().getHeight());
mediaRecorderConfiguration.setFps(recorderConfig.getVideo().getFps());
mediaRecorderConfiguration.setStoragePath(recorderConfig.getRecorderPath());
mediaRecorderConfiguration.setSampleRate(recorderConfig.getAudio().getSampleRate());
mediaRecorderConfiguration.setChannelNum(recorderConfig.getAudio().getNumOfChannels());
mediaRecorderConfiguration.setStreamType(Constants.MediaRecorderStreamType.STREAM_TYPE_BOTH);
mediaRecorderConfiguration.setMaxDurationMs(recorderConfig.getMaxDuration() * 1000);
agoraMediaRtcRecorder.setRecorderConfig(mediaRecorderConfiguration);
// Join channel and start recording
agoraMediaRtcRecorder.joinChannel(recorderConfig.getToken(),
recorderConfig.getChannelName(),
recorderConfig.getUserId());
agoraMediaRtcRecorder.startRecording();
// Stop recording, clean up resources, and leave channel
agoraMediaRtcRecorder.unsubscribeAllAudio();
agoraMediaRtcRecorder.unsubscribeAllVideo();
agoraMediaRtcRecorder.stopRecording();
agoraMediaRtcRecorder.unregisterRecorderEventHandler(eventHandler);
agoraMediaRtcRecorder.leaveChannel();
agoraMediaRtcRecorder.release();
agoraService.release();
```
## Test the project [#test-the-project-1]
Agora provides an open source sample project on GitHub for your reference. Download or view the [Agora Recording Java SDK](https://github.com/AgoraIO-Extensions/Agora-Recording-Java-SDK) project for a more detailed example.
Follow these steps to compile, configure, and run the Agora recording sample using the Spring Boot project.
1. **Compile the project**
Open a terminal and navigate to the `Examples-Mvn` directory. Run the following command to build the project:
```bash
mvn clean package
```
After the build completes successfully, the output JAR file appears in the `target/generated` directory as `agora-example.jar`.
2. **Configure your App ID and token**
In the `Examples-Mvn` directory, create a `.keys` file and add your Agora credentials:
```text
appId=YOUR_APP_ID
token=YOUR_TOKEN
```
Replace `YOUR_APP_ID` and `YOUR_TOKEN` with your actual values.
3. **Prepare native libraries**
Ensure that the `libs/native/linux/x86_64/` directory contains the following `.so` files:
* `libagora_rtc_sdk.so`
* `librecording.so`
* `libagora-fdkaac.so`
* `libaosl.so`
These libraries must be available at runtime. If any are missing, the application will fail to load the native dependencies.
4. **Run the sample application**
From the `Examples-Mvn` directory, run the following command:
```bash
LD_LIBRARY_PATH="$LD_LIBRARY_PATH:libs/native/linux/x86_64" \
java -Dserver.port=18080 -jar target/agora-example.jar
```
The Spring Boot server starts and listens on port `18080`. To use a different port, update the `-Dserver.port` parameter.
5. **Start and stop a recording session**
* **Start recording**
Send an HTTP request to the following endpoint:
```text
http://:18080/api/recording/start?configFileName=mix_stream_recorder_audio_video_water_marks.json
```
* **Stop recording**
Replace `` with the value returned from the start response:
```text
http://:18080/api/recording/stop?taskId=
```
Place all configuration files (such as `mix_stream_recorder_audio_video_water_marks.json`) in the `Examples-Mvn/src/main/resources/` directory.
## Reference [#reference-1]
This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.
### API reference [#api-reference-1]
* [`AgoraService`](/en/api-reference/api-ref/on-premise-recording#agoraservice)
* [`AgoraServiceConfiguration`](/en/api-reference/api-ref/on-premise-recording#agoraserviceconfiguration)
* [`LogConfig`](/en/api-reference/api-ref/on-premise-recording#logconfig)
* [`initialize`](/en/api-reference/api-ref/on-premise-recording#initialize)
* [`AgoraMediaRtcRecorder`](/en/api-reference/api-ref/on-premise-recording#agoramediartcrecorder)
* [`registerRecorderEventHandler`](/en/api-reference/api-ref/on-premise-recording#registerrecordereventhandler)
* [`subscribeAllAudio`](/en/api-reference/api-ref/on-premise-recording#subscribeallaudio)
* [`subscribeAllVideo`](/en/api-reference/api-ref/on-premise-recording#subscribeallvideo)
* [`setRecorderConfig`](/en/api-reference/api-ref/on-premise-recording#setrecorderconfig)
* [`joinChannel`](/en/api-reference/api-ref/on-premise-recording#joinchannel)
* [`startRecording`](/en/api-reference/api-ref/on-premise-recording#startrecording)
* [`unsubscribeAllAudio`](/en/api-reference/api-ref/on-premise-recording#unsubscribeallaudio)
* [`unsubscribeAllVideo`](/en/api-reference/api-ref/on-premise-recording#unsubscribeallvideo)
* [`stopRecording`](/en/api-reference/api-ref/on-premise-recording#stoprecording)
* [`unregisterRecorderEventHandler`](/en/api-reference/api-ref/on-premise-recording#unregisterrecordereventhandler)
* [`leaveChannel`](/en/api-reference/api-ref/on-premise-recording#leavechannel)
* [`release`](/en/api-reference/api-ref/on-premise-recording#release)
<_PlatformProcessedMarker close="true" />
# Agora skills (/en/realtime-media/on-premise-recording/skills)
Agora skills are a set of structured reference files that give AI coding assistants deep knowledge of Agora's platform. When you ask your assistant to build something with Agora, it loads the relevant skill files covering products, APIs, and platform-specific code examples, so it can generate working code without guessing.
Skills include the [Agora MCP server](./mcp.mdx), which gives your assistant access to live Agora documentation. For installation instructions and supported tools, see the [Agora Skills repository](https://github.com/AgoraIO/skills).
## Installation [#installation]
```bash
npx skills add github:AgoraIO/skills
```
Skills activate automatically when your agent detects relevant tasks, for example, "build a voice agent", "integrate Agora RTC", or "generate a token".
## Manual installation [#manual-installation]
Clone the repository once and point your AI coding assistant to the skill files directly.
1. Clone the [Agora Skills repo](https://github.com/AgoraIO/skills.git):
```bash
git clone https://github.com/AgoraIO/skills.git ~/agora-skills
```
2. Point your AI assistant to `skills/agora/`.
Follow the instructions for your AI coding assistant:
### Claude Code [#claude-code]
Run the following commands inside Claude Code:
* User-level, available across all your projects:
```bash
/plugin marketplace add AgoraIO/skills
/plugin install agora
```
* Project-level, shared with your team via version control:
```bash
/plugin marketplace add AgoraIO/skills
/plugin install agora --scope project
```
### Cursor [#cursor]
Copy or symlink `skills/agora/` into `.cursor/rules/`. For more information, see [Cursor skill directories](https://cursor.com/docs/skills#skill-directories).
### Windsurf [#windsurf]
Add `skills/agora/` to your Cascade context. For more information, see [Windsurf skills](https://docs.windsurf.com/windsurf/cascade/skills).
### GitHub Copilot [#github-copilot]
Reference via `@workspace` or add to `.github/copilot-instructions.md`. For more information, see [Create skills for Copilot in the CLI](https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/create-skills) or [Create skills for the Copilot coding agent](https://docs.github.com/en/copilot/how-tos/use-copilot-agents/coding-agent/create-skills).
### Other tools [#other-tools]
The skill files are plain Markdown. Point your tool to `skills/agora/` or load individual files directly. Use `SKILL.md` as the entry point.
# Online KTV (/en/realtime-media/online-ktv)
## What this product covers [#what-this-product-covers]
Online KTV combines realtime audio interaction, room coordination, music-content workflows, and karaoke-specific experience design into one product surface.
## Product paths [#product-paths]
* [Scenario API](scenario-api.md)
* [PaaS SDK](paas-sdk.md)
* [UIKit Open Source](uikit.md)
## Recommended reading path [#recommended-reading-path]
1. Start with the path that best matches your integration model.
2. Validate whether you need a scene-oriented API, a lower-level PaaS path, or a UIKit-based fast-start approach.
3. Expand into detailed implementation docs as the legacy KTV content is normalized into this new product directory.
## Current migration note [#current-migration-note]
The deeper Online KTV source materials already exist in this repository, but the legacy multi-level MDX content still depends on older portal components. This product directory is the stable new-site entry point while that deeper content is being migrated.
# PaaS SDK (/en/realtime-media/online-ktv/paas-sdk)
## What it is [#what-it-is]
The PaaS SDK path gives teams more control over business logic, room interaction, and UI design while still building on Agora's realtime and music-related capabilities.
## Best fit [#best-fit]
* teams that want deeper customization
* products with existing app-specific room logic or UI systems
* engineering teams comfortable integrating RTC, RTM, and karaoke workflows directly
## Current status in this repo [#current-status-in-this-repo]
The detailed source materials for this path already exist under the legacy `online-ktv` content tree and will be migrated into this new directory model incrementally.
# Scenario API (/en/realtime-media/online-ktv/scenario-api)
## What it is [#what-it-is]
The Scenario API path wraps lower-level realtime APIs into karaoke-specific flows so teams can build solo singing, chorus, relay singing, and competition-style KTV rooms faster.
## Best fit [#best-fit]
* teams optimizing for delivery speed
* products centered on karaoke room interaction
* workloads that benefit from scene-oriented APIs instead of raw infrastructure assembly
## Current status in this repo [#current-status-in-this-repo]
The detailed source materials for this path already exist under the legacy `online-ktv` content tree and will be migrated into this new directory model incrementally.
# UIKit Open Source (/en/realtime-media/online-ktv/uikit)
## What it is [#what-it-is]
The UIKit Open Source path provides reusable karaoke UI components so teams can build an Online KTV product with less custom interface work.
## Best fit [#best-fit]
* teams that want a UI-first jumpstart
* prototypes and pilots that need faster visual assembly
* products that prefer extending open-source karaoke components over designing from scratch
## Current status in this repo [#current-status-in-this-repo]
The detailed source materials for this path already exist under the legacy `online-ktv` content tree and will be migrated into this new directory model incrementally.
# Server Gateway overview (/en/realtime-media/rtc-server-sdk)
Agora's Server Gateway service is a self-hosted solution that enables seamless transmission of audio and video streams between server-side applications and Agora’s Voice and Video SDKs using the Agora SDRTN®. It allows cloud applications to take full advantage of Agora’s global real-time network while maintaining control over their infrastructure.
Designed for flexibility, Server Gateway supports a range of use-cases such as call centers, network testing, and AI-powered interactive classes. With multi-format support for YUV, PCM, and encoded media, configurable workflows for sending and receiving streams, and multi-channel capabilities, it offers exceptional flexibility for integrating media streams. Additional features include audio mixing, media encryption, and zone restrictions to ensure secure and localized media delivery.
## Start building [#start-building]
## Product Features [#product-features]
Support for use-cases like call centers, network testing, and AI-powered interactive classes with a single codebase for Application Platform as a Service (aPaaS) applications.
Supports sending and receiving data in various formats, such as YUV, PCM and encoded video and audio formats which provides greater flexibility for customers using Agora’s global network.
Send and receive media streams simultaneously or choose only to send or receive audio or video streams. Server Gateway also supports pushing media streams to a CDN directly.
Supports sending and/or receiving media streams to and from multiple channels simultaneously.
Support for media encryption and mixing multiple audio streams.
The Agora Voice and Video SDKs support restricting media zones, so that they only connect to Agora servers within the selected zone(s), regardless of where your app users are located.
# Integrate the SDK (/en/realtime-media/rtc-server-sdk/quickstart)
<_PlatformTabsGroup groupMode="structured" canonicalPlatform="linux-cpp" platforms="["linux-cpp","linux-java","python","go"]" showTabs="true">
<_PlatformPanel platform="linux-cpp">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="linux-cpp" platform="linux-cpp" />
This article shows how to integrate the Server Gateway C++ SDK and run the sample project.
## Set up the development environment [#set-up-the-development-environment]
Make sure your server meets the following requirements.
### Hardware environment [#hardware-environment]
**Operating system**
* Ubuntu 14.04 or higher
* CentOS: 7.0 or higher
**CPU architecture**
* arm64
* x86-64
If you need to run the SDK on other architectures, [submit a ticket](https://agora-ticket.agora.io/) to contact technical support.
**Performance**
* CPU:8-core, 1.8 GHz or higher.
* 2 GB of RAM or higher. 4 GB or higher is recommended.
**Network**
* The server is connected to the internet and has an internet IP.
* The server can access `*.agora.io` and `*.agoralab.co`.
### Software environment [#software-environment]
* glibc 2.18 or later
* gcc 4.8 or later
If you are using Ubuntu, taking Ubuntu 20.04.3 LTS as an example, install the following dependencies in your server:
```bash
# Install aptitude
sudo apt install aptitude
# Install build-essential libx11-dev libxcomposite-dev libxext-dev libxfixes-dev libxdamage-dev cmake
sudo aptitude install libx11-dev libxcomposite-dev libxext-dev libxfixes-dev libxdamage-dev cmake
```
If you are using CentOS, taking CentOS 7.9.2009 as an example, install the following dependencies in your server:
```bash
sudo yum groupinstall "Development Tools"
sudo yum install wget
sudo yum groupinstall X11
```
## Get an Agora App ID and a Video SDK Temporary Token [#get-an-agora-app-id-and-a-video-sdk-temporary-token]
See [Get Started with Agora](build/manage-agora-account.md) to learn how to get an **Agora App ID** and a **Video SDK temporary token**.
## Get the SDK [#get-the-sdk]
[Download](/en/api-reference/sdks?product=server-gateway\&platform=linux) the latest x86-64 SDK package and decompress the file. If you need the SDK build for other architectures, [submit a ticket](https://agora-ticket.agora.io/) to technical support.
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="linux-java">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="linux-cpp" platform="linux-java" />
This article shows how to create a simple Maven project, integrate the Server Gateway Java SDK, and run the app.
## Set up the development environment [#set-up-the-development-environment-1]
Make sure your server meets the following requirements.
### Hardware environment [#hardware-environment-1]
**Operating system**
* Ubuntu 18.04 or higher
* CentOS: 7.0 or higher
**CPU architecture**
* arm64
* x86-64
If you need to run the SDK on other architectures, [submit a ticket](https://agora-ticket.agora.io/) to contact technical support.
**Performance**
* CPU:8-core, 1.8 GHz or higher.
* 2 GB of RAM or higher. 4 GB or higher is recommended.
**Network**
* The server is connected to the internet and has an internet IP.
* The server can access `*.agora.io` and `*.agoralab.co`.
### Software environment [#software-environment-1]
* [Apache Maven](https://maven.apache.org/download.cgi) or other build tools. This page uses Apache Maven as an example.
* JDK 8
## Get an Agora App ID and a Video SDK Temporary Token [#get-an-agora-app-id-and-a-video-sdk-temporary-token-1]
See [Get Started with Agora](build/manage-agora-account.md) to learn how to get an **Agora App ID** and a **Video SDK temporary token**.
## Create a Maven project [#create-a-maven-project]
See [Maven in Five Minutes](https://maven.apache.org/guides/getting-started/maven-in-five-minutes.html) to create a Maven project.
```bash
mvn archetype:generate -DgroupId=com.mycompany.app -DartifactId=my-app -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.4 -DinteractiveMode=false
```
## Integrate the SDK [#integrate-the-sdk]
1. Navigate to the `my-app` folder.
```bash
cd my-app
```
2. Open `pom.xml` and replace the content with the following:
```xml
4.0.0org.exampleagora-rtc-linux-java1.0-SNAPSHOTio.agora.rtclinux-sdk3.7.200.211.81.8org.apache.maven.pluginsmaven-shade-plugin2.3truepackageshadeApp
```
Refer to [mvnrepository](https://mvnrepository.com/artifact/io.agora.rtc/linux-sdk) for other integration methods, such as Gradle.
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="python">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="linux-cpp" platform="python" />
This article shows how to integrate the Server Gateway Python SDK and run the sample project.
## Set up the development environment [#set-up-the-development-environment-2]
Make sure your server meets the following requirements.
### Hardware environment [#hardware-environment-2]
**Operating system**
* Ubuntu 18.04 or higher
* CentOS: 7.0 or higher
**CPU architecture**
* x86-64
If you need to run the SDK on other architectures, [submit a ticket](https://agora-ticket.agora.io/) to contact technical support.
**Performance**
* CPU:8 cores, 1.8 GHz or higher.
* 2 GB of RAM or higher. 4 GB or higher is recommended.
**Network**
* The server is connected to the internet and has an internet IP.
* The server can access `*.agora.io` and `*.agoralab.co`.
### Software environment [#software-environment-2]
* An IDE that supports Python, such as PyCharm or Visual Studio Code.
* Python 3.10 or above
## Get an Agora app ID and a Video SDK temporary token [#get-an-agora-app-id-and-a-video-sdk-temporary-token-2]
See [Get Started with Agora](build/manage-agora-account.md) to learn how to get an **Agora app ID** and a **Video SDK temporary token**.
## Download and install the SDK [#download-and-install-the-sdk]
Run the following command to get the latest SDK.
```bash
pip install agora_python_server_sdk
```
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="go">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="linux-cpp" platform="go" />
This article shows how to integrate the Server Gateway Go SDK and run the sample project.
## Set up the development environment [#set-up-the-development-environment-3]
Make sure your server meets the following requirements.
### Hardware environment [#hardware-environment-3]
**Operating system**
* Ubuntu 18.04 or higher
* CentOS: 7.0 or higher
**CPU architecture**
* x86-64
If you need to run the SDK on other architectures, [submit a ticket](https://agora-ticket.agora.io/) to contact technical support.
**Performance**
* CPU:8-core, 1.8 GHz or higher.
* 2 GB of RAM or higher. 4 GB or higher is recommended.
**Network**
* The server is connected to the internet and has an internet IP.
* The server can access `*.agora.io` and `*.agoralab.co`.
### Software environment [#software-environment-3]
* Go 1.21 or above
## Get an Agora app ID and a Video SDK temporary token [#get-an-agora-app-id-and-a-video-sdk-temporary-token-3]
See [Get Started with Agora](build/manage-agora-account.md) to learn how to get an **Agora app ID** and a **Video SDK temporary token**.
## Get the SDK [#get-the-sdk-1]
Run the following command to get the latest SDK.
```bash
git clone https://github.com/AgoraIO-Extensions/Agora-Golang-Server-SDK.git go_rtc_sdk
```
## Compile the sample project [#compile-the-sample-project]
Run the following commands to compile the sample project:
```bash
# Enter the SDK directory
cd go_rtc_sdk
# Execute the build
make build
```
## Add the required dependencies [#add-the-required-dependencies]
In the project’s `go.mod` or builds file, add the following lines to include the required SDK dependencies.
```bash
# Replace /path/to/go_rtc_sdk with the actual path of the SDK you downloaded in Step 1
replace github.com/AgoraIO-Extensions/Agora-Golang-Server-SDK/v2 => /path/to/go_rtc_sdk
# Replace version_number with the actual SDK version number, which you can find in the release notes, e.g., v2.1.0
require github.com/AgoraIO-Extensions/Agora-Golang-Server-SDK/v2 version_number
```
<_PlatformProcessedMarker close="true" />
# Beginner's guide (/en/realtime-media/rtm/beginners-guide)
## What is Signaling? [#what-is-signaling]
Signaling provides a comprehensive suite of low-latency, high-concurrency, scalable, and highly-reliable real-time messaging and status synchronization solutions. Agora manages the infrastructure required for the real-time communication layer of your game, guaranteeing an SLA uptime of 99.95%. To facilitate user development and innovation, Agora offers a plethora of demo apps and open third-party API extensions.
Agora Signaling, built on extensive technical expertise and global network deployment, is diligently maintained by a team of seasoned product and R\&D professionals. By integrating Signaling into your game, you can leverage Agora's professional knowledge and operational expertise, ensuring swift access to dependable message and signal transmission, as well as real-time status synchronization data flow networks. This approach allows you to bypass the high costs and potential risks associated with self-development, operation, and maintenance.
## Applications [#applications]
Signaling is widely used by more than 3000 customers in the following fields:
* Metaverse
* Interactive gaming
* Online education
* E-commerce retail
* Interactive live broadcast
* Collaboration
* IoT and smart devices
* Telemedicine
* Transportation and location tracking
* Fintech
* Parallel control
* Smart city
To learn more about specific use-cases, [contact](mailto\:rtm-support@agora.io) the Agora Signaling team.
## Getting started [#getting-started]
To create and set up a project for Signaling in Agora Console, see [Agora account management](./manage-agora-account.md). Browse the following documents to get started with Signaling:
* [SDK quickstart](./index.mdx)
* [Message channels](./build/work-with-channels/message-channel.mdx)
* [Stream channels](./build/work-with-channels/stream-channel.mdx)
* [API reference](/en/api-reference/api-ref/signaling)
* [Pricing](./reference/pricing.md)
# Agora console overview (/en/realtime-media/rtm/console-overview)
[Agora Console](https://console.agora.io/v2) is the unified portal for you to configure, purchase, and manage Agora products and services. This page shows you how to use the new **beta** version of Agora Console to manage all aspects of your Agora account.
## Dashboard overview [#dashboard-overview]
After logging in to Agora Console, you see the overview dashboard. The dashboard displays a summary of important statistics and provides quick access to projects, billing, documentation, and code samples.

Agora Console provides access to the following:
* Create and manage projects
Create a project, and get the App ID and App Certificate to generate a temporary token. See [Create and manage projects](#create-and-manage-projects).
* Enable and configure features
Configure Chat, Signaling, Interactive Whiteboard, Cloud Recording, Media Push, Media Pull, Notifications, Flexible Classroom, Cloud proxy, and Co-Host Authentication.
* Check usage
Check the usage amount generated by each project or all of them. See [Check usage](#check-usage).
* Manage members and roles
Add members to your account, and add them to different teams with specified permissions. See [Manage members and roles](#manage-members).
* Manage billing
Add money to your account, view your transactions, and make withdrawal requests. See [Billing center](#billing-center).
* Submit a ticket
If you have any questions about Agora products, [submit a ticket](#submit-a-ticket).
* Generate Customer ID and Customer Secret
To use Agora RESTful APIs, you need a set of Customer ID and Customer Secret. See [Generate a customer ID and customer secret](#generate-a-customer-id-and-customer-secret).
* Agora Analytics
Track and analyze the usage and quality data of real-time communication products through highly visualized reports in order to spot issues and find root causes. For more information, see the [Agora Analytics overview](/en/realtime-media/agora-analytics/product-overview).
* Agora Extensions Marketplace
Gain access to extensions that rapidly add fun features to your app on top of Agora SDKs. For more information, see the [Extensions Marketplace overview](/en/realtime-media/marketplace).
## Manage profile [#manage-profile]
This section shows how to view and edit the profile of your Agora account.
### Open the profile page [#open-the-profile-page]
To open the **Profile** page, follow these steps:
1. Log in to [Agora Console](https://console.agora.io/v2).
2. Click your account name in the top right corner, and select your account name from the dropdown menu.
On the **Profile** page, you can view and edit your phone number, email address, password, and login methods.

### Change phone number, email address, or password [#change-phone-number-email-address-or-password]
1. Find **Phone Number**, **Email**, or **Password** fields on the profile page, then click **Update**.
2. Follow the on-screen instructions to complete.
### Link a third-party account [#link-a-third-party-account]
1. On the profile page, find **GitHub**, **WeChat**, or **Google**, then click **Connect**.
2. Follow the on-screen instructions to complete.
### Reset your password [#reset-your-password]
If you forget your password, follow these steps to reset it:
1. Go to the [login page](https://sso.agora.io/login/) and click **Forgot password**.
2. Fill in your email address and click **Send email**. Agora sends an email to you.
3. Follow the instructions in the email to reset your password.
## Create and manage projects [#create-and-manage-projects]
This section shows how to create a project, view project information, and manage App Certificates in Agora Console.
### Restrictions [#restrictions]
* If your account has multiple members, only those assigned to the teams of **Admin**, **Engineer**, or an authorized custom team have access to the **Projects** page.
* Each Agora account can create up to 20 projects. If you need to create more projects, contact Agora by [submitting a ticket](https://agora-ticket.agora.io/).
### Create an Agora project [#create-an-agora-project]
To create a project, do the following:
1. Open the [Projects](https://console.agora.io/v2/project-management) page.
2. Click **Create New**.
3. Follow the on-screen instructions to enter a project name and use case, and check **Secured mode: APP ID + Token (Recommended)** as the authentication mechanism.
4. Click **Submit**. You can now see the project on the **Projects** page.
### View project information [#view-project-information]
For a project listed on the **Projects** page, you can do the following:
* View basic information, such as the last updated and created dates, name, and security status.
* Copy the [App ID](reference/glossary.md) of the project.
* Click the pencil icon to open the project details page, where you can configure **Project Name**, copy **App Certificate** and **App ID**, and generate a **Temporary Token**.
### Manage app certificates [#manage-app-certificates]
An App Certificate is a string generated by Agora Console to enable token authentication. For different security requirements, Agora provides the following app certificate options:
* Primary Certificate: This certificate is used to generate tokens for temporary use or production environments.
* Secondary Certificate: This certificate is used to generate tokens for production environments only and does not apply to RESTful APIs.
### Enable the Primary Certificate [#enable-the-primary-certificate]
For high-security use-cases, enable the Primary Certificate as follows:
* If you choose **APP ID + Token** as the authentication mechanism when creating a project, the Primary Certificate is enabled by default. On the project details page, you can click the copy icon under **Primary Certificate** to view and copy it. In this case, you need to use the token generated by the **Primary Certificate** for authentication.
* If you choose **APP ID** as the authentication mechanism when creating a project, you need to enable the Primary Certificate manually. On the project detail page, click **Enable** under **Primary Certificate**.

Once the Primary Certificate is enabled, you can click the copy icon to view and copy it. In this case, you can either complete authentication with the App ID only or use tokens generated by the **Primary Certificate** for authentication.
### Enable the Secondary Certificate [#enable-the-secondary-certificate]
If you need to change the Primary Certificate after enabling it, you can enable the Secondary Certificate.
On the project detail page, click **Add a Certificate**.

The **Secondary Certificate** is now enabled.

Once the Secondary Certificate is enabled, you can click the copy icon to view and copy it. In this case, both the Primary and Secondary Certificate can be used to generate tokens for authentication.
### Switch to a new Primary Certificate [#switch-to-a-new-primary-certificate]
If you suspect that your primary certificate has been compromised, swap the primary certificate with the secondary certificate and delete the original primary certificate to avoid risks. Replacing the primary certificate may impact your online business. Best practice is to replace the primary certificate only when necessary. If you replace the primary certificate but do not delete the secondary certificate (the original primary certificate), the tokens previously generated using this certificate can still be used.
To switch to a new Certificate, follow these steps:
1. [Enable the Secondary Certificate](#enable-the-secondary-certificate).
2. On the project detail page, click the arrows icon between the Certificates to swap them.
3. Turn off the switch next to the **Secondary Certificate** to disable it.

4. Follow the on-screen instructions to complete verification. If verified successfully, the status of the Secondary Certificate is updated to **Disabled**, and the **Delete** icon appears.

5. Click the **Delete** icon.
6. Follow the on-screen instructions to complete verification. If verified successfully, the **Delete** button disappears, and the current Secondary Certificate (the original Primary Certificate) is deleted.
Once a certificate is deleted, it cannot be restored. Before deleting the secondary certificate, make sure that most users have switched to the new certificate to avoid the following consequences:
```
- All tokens generated using the deleted certificate become invalid, and users can no longer use these tokens to join channels.
- Users who have joined the channel cannot specify new tokens through `renewToken`.
```
## Check usage [#check-usage]
This section explains how to check your usage of Agora products in Agora Console, such as Interactive Live Streaming, Broadcast Streaming, Media Push, On-Premise Recording, Cloud Recording, and Interactive Whiteboard.
### Restrictions [#restrictions-1]
* If your account has multiple members, only those assigned to the team of **Admin**, **Product/Operation**, or an authorized custom team have access to the **Usage** page.
* You can only view the usage data for the past 12 months.
* Data on the **Usage** page can differ from the actual amount. For the most accurate usage data, see the bills sent to your account.
### Procedure [#procedure]
Follow these steps to check your usage:
1. Enter the [Usage](https://console.agora.io/v2/usage) page in Agora Console.
2. (Optional) Add filters for the usage data, as follows:
* Select a time frame and data granularity.
* Select **All Projects** or a specific project from the dropdown menu.
* Select a product to view its usage data.
* Check **View Breakdown**.

## Manage members and teams [#manage-members-and-teams]
Once you add members to your Agora account, you are assigned to the team of **Admin** and can add these members to different teams with specified permissions.
### Add a member [#add-a-member]
Follow these steps to add a member to your account:
1. Log in to [Agora Console](https://console.agora.io/v2), click your account name in the top-right corner, and click **Settings** in the dropdown menu.
2. In the left navigation panel, click **Teams and members**, then select the **Members** tab.
3. Click the **Add New Member** button, fill in the email address of the new member, and choose a team from the dropdown menu. Then click **OK**.

4. Agora sends a confirmation email to this address. The new member should follow the instructions in the email to finish joining the project.
### Manage members [#manage-members]
On the [Teams and members](https://console.agora.io/v2/teams-members) page, **Members** tab, you can do the following:
* Click the pencil icon to change the team that a member belongs to.
* Delete a member.
Only the main account can delete a member account. Member accounts assigned to the Admin team cannot delete a member account.
### Manage teams and permissions [#manage-teams-and-permissions]
On the **Teams and members** page, **Teams** tab, you can view the permissions assigned to teams. Agora predefines five teams for you. The permissions of each predefined team are as follows:
* **Admin** has full access to all projects. The administrator can view usage data and finance information, manage members and teams, manage projects, and view Agora Analytics reports.
* **Finance** can view finance information of all projects.
* **Product/Operation** can view usage data of all projects.
* **CS/Maintenance** can view Agora Analytics reports of all projects.
* **Engineer** can manage projects and view Agora Analytics reports of all projects.
### Add a custom team [#add-a-custom-team]
Follow these steps to add a custom team:
1. On the **Teams and Members** page, **Teams** tab, click **Add New Team**.

2. Fill in the team name, and select the permissions of this team in the **Usage**, **Finance**, **Teams and Members**, **Project**, **Agora Analytics**, and **Data** columns.
3. Click **OK**.
## Integrate with Okta [#integrate-with-okta]
If you are using [Okta](https://www.okta.com/) as the identity provider for your app, you can configure Agora Console so that your team members can log into Agora using their Okta accounts. You will need:
* An Okta developer account with admin privileges
* An Agora account assigned to the **Admin** team
* A Premium or Enterprise Agora Support package
### Configuration steps [#configuration-steps]
1. Apply for Okta integration in Agora Console.
Log in to your Agora account. On the [SSO Management](https://console.agora.io/settings/sso-management) page, click **Request**.

Once you apply, a request is sent to Agora to enable integration for you.

When your application is processed by Agora, **SAML Configuration** on the same page is automatically enabled and a set of **SSO URL** and **Audience URI** is generated.
2. Create a SAML integration for Agora in Okta Console.
1. Log in to your Okta developer account as a user with administrative privileges and click **Admin** in the upper right corner.

2. Go to **Applications** > **Applications** and click **Create App Integration**.

3. In the **Sign-in method** section, select **SAML 2.0** and click **Next**.
4. On the **General Settings** tab, enter a name for this integration, for example, `Agora`, and click **Next**.
5. On the **Configure SAML** tab, fill in the following SAML configurations generated in Agora Console:
* In the **Single sign on URL** field, enter:
```text
https://sso2.agora.io/api/v0/saml/idp/callback
```
* In the **Audience URI (SP Entity ID)** field, enter:
```text
https://sso2.agora.io/{companyId}/saml/SSO
```
where `{companyId}` is your company ID.
* In the **Attribute Statements (optional)** section, add the following attribute:
| Name | Value |
| :------ | :----------- |
| `email` | `user.email` |

6. On the **Feedback** tab, select **I'm an Okta customer adding an internal app** and tick **This is an internal app that we have created**, then click **Finish**.

3. View IdP information in Okta Console
1. In Okta Console, select the **Sign On** tab for Agora integration and click **View Setup Instructions**. You see the following information:
* **Identity Provider Single Sign-On URL**: At the end of this procedure, your team members use this URL to sign in to Agora Console
* **Identity Provider Issuer**
* **X.509 Certificate**

4. Configure SAML settings in Agora Console
In the **SAML Configuration** section on the [SSO Management](https://console.agora.io/settings/sso-management) page in Agora Console, enter the following information from Okta Console:
* **Identity Provider Single Sign-On URL**
* **Identity Provider Issuer**
* **X.509 Certificate**, including the `BEGIN CERTIFICATE` and `END CERTIFICATE lines`.
Click **Save**.
5. Manage access
You can manage access for your team members manually using SAML, which is enabled when you apply for Okta
integration in Agora Console, or automatically, if you additionally enable SCIM. Manual management means that adding, managing, and removing a team member's access must be done in both, Okta and Agora Console. Automatic management means that SCIM automatically adds, manages, and removes members' access to Agora Console when you make changes in Okta.
**Note**: Once enabled, SAML is applicable for both, existing and newly created Agora accounts for team members. For new accounts, however, log in as the admin in Agora Console and create an account *before* it is configured in Okta. SCIM is applicable only for new accounts.
* Manual management with SAML
1. In Agora Console, add team members and choose their teams. For details, see [Manage members and teams](#manage-members-and-teams).
2. In Okta Console, go to **Directory** > **People**. Ensure that the email address of each team member is the same as that in Agora Console.
3. Go to **Applications** > **Applications** > **Agora integration**.
4. On the **Assignments** tab, click **Assign** and select **Assign to People**.
5. Enter the team members that need to sign in to Agora Console and click **Assign** for each. Click **Done**.
* Automated management with SCIM
1. In Agora Console, enable **SCIM API Basic Auth** on the **SSO Management** page. A set of username and password is generated, along with the SCIM connector base URL.

2. In Okta Console, select the **General** tab under **Agora integration** and click **Edit**.
3. In the **Provisioning** section, select **SCIM** and click **Save**.
4. Select the **Provisioning** tab under **Agora integration**, then under **Settings** > **Integration** click **Edit**. Make the following changes:
* In **SCIM connector base URL**, enter the corresponding URL from Agora Console.
* In **Unique identifier field for users**, enter `email`.
* Under **Supported provisioning actions**, tick all the checkboxes.
* In **Authentication Mode**, select **Basic Auth**.
* Under the **Basic Auth** section, enter the username and password from the SSO management page in Agora Console.

After enabling and configuring Okta integration, the [Big Bang feature](https://help.okta.com/oie/en-us/content/topics/reference/glossary.htm) is enabled by default. This means that login
to Agora Console with email and password is no longer available and your team members can log in only through Okta.
## Billing center [#billing-center]
This section shows how to check your account balance, add money to your account, view transactions, and make a withdrawal in Agora Console.
### Restrictions [#restrictions-2]
If your account has multiple members, only those assigned to the teams of **Admin**, **Finance**, or an authorized custom team have access to the **Billing** page.
### Check account balance [#check-account-balance]
After logging in to [Agora Console](https://console.agora.io/v2), you can see your account balance in the **Billing** panel on the **Overview** page.
### Add money to account [#add-money-to-account]
You can add money to your account either with a credit card or via bank transfer.
### Credit card [#credit-card]
To add money to your account with a credit card, follow these steps:
1. In [Billing](https://console.agora.io/v2/billing), select the **Credit Card** tab.
2. (Optional) If you have not added a credit card to your account, follow these steps:
1. Click **Add New Card**.

2. Enter the card number, card holder name, expiration date, and CVC.
3. Choose whether or not to set this card as the default credit card.
4. Click **Continue**.
3. Select a credit card, enter the **Payment Amount**, and click **Pay**. If the payment is successful, you see a "Payment Succeeded" message.
### Bank transfer [#bank-transfer]
To add money to your account via bank transfer, follow these steps:
1. In [Billing](https://console.agora.io/v2/billing), select the **Bank Transfer** tab.

2. Follow the on-screen instructions to complete.
### View invoices and transactions [#view-invoices-and-transactions]
In [Billing](https://console.agora.io/v2/billing), scroll down to see **Your Invoices** and **Your Transactions**, respectively.
You can also click **View all** > **Export CSV** in the top right corner to export transactions and invoices as a CSV file.
## Submit a ticket [#submit-a-ticket]
To ask Agora support a question, follow these steps:
1. Log in to [Agora Console](https://console.agora.io/v2).
2. Click **Get Support** > **Create Support Ticket**

3. Type in your question or keywords to see if the question has been answered. If you cannot find your answer, select a category and submit a ticket to Agora’s customer support.
You can track the status of your ticket under **Tickets**.
## Delete your Agora account [#delete-your-agora-account]
This section describes how to delete your Agora account created with an email address or phone number.
Before deleting your Agora account, ensure that the following requirements are met:
* You are the creator of the account to be deleted.
* The account to be deleted is an Agora account created in [Agora Console](https://console.agora.io/v2), not a reseller account created in [Agora Reseller Console](https://reseller.agora.io/).
* There are no active projects under your account. If there are any, go to the [Projects](https://console.agora.io/v2/project-management) page to disable all active projects.
* Your account balance is zero. If your account balance is positive, request a withdrawal. If your account balance is negative, add money to your account.
* Your bill for the previous month has been issued.
* No usage is generated in the current month.
* All of your packages have either expired or been used up, including:
* Video SDK and Cloud Recording packages.
* Support packages.
* Extensions Marketplace packages.
* There are no members under your account. If there are any, go to the [Teams and Members](https://console.agora.io/v2/teams-members) page to delete all members.
### Procedure [#procedure-1]
Follow these steps to delete your Agora account:
1. Log in to [Agora Console](https://dashboard.agora.io/), click your account name in the top right corner and select **Settings** in the dropdown.
2. In the left navigation panel, click **Account**.

3. Ensure that you meet all the prerequisites, and click **Delete Account** at the bottom of the page.
4. Read the pop-up prompt carefully and fill in the confirmation field. Click **Delete Account**.
If any prerequisite is not met, an error message appears after you click **Delete Account**. Check the [Prerequisites](#prerequisites) and try again.
## Reference [#reference]
For information on using Agora REST authentication, see [RESTful authentication](/en/api-reference/api-ref/signaling/authentication).
# Core concepts (/en/realtime-media/rtm/core-concepts)
Agora’s Signaling SDK enables real-time metadata synchronization and low-latency event notifications between edge devices, servers and channel attributes in your apps.
This article introduces the key processes and concepts you need to know to use the Signaling SDK.
## General concepts [#general-concepts]
Agora relies on the following fundamental concepts to enable seamless real-time communication:
### Agora SDRTN® [#agora-sdrtn]
Agora's core engagement services are powered by its Software-Defined Real-Time Network (SDRTN®), a global infrastructure accessible anytime, anywhere. Unlike traditional networks, Agora SDRTN® is not restricted by devices, phone numbers, or telecom coverage areas. With data centers in over 200 countries and regions, it ensures sub-second latency and high availability for real-time media.
Agora SDRTN® enables live user engagement through real-time communication (RTC), offering:
* Unmatched quality of service
* High availability and accessibility
* True scalability
* Low cost
### App ID [#app-id]
The App ID is a unique key generated by Agora to identify each project and provide billing and other statistical data services. The App ID is critical for connecting users within your app. It is used to initialize the Agora Engine in your app, and as one of the required keys to create authentication tokens for secure communication. Retrieve the App ID for your project using the [Agora Console](https://console.agora.io/v2/project-management).
App IDs are stored on the front-end client and do not provide access control. Projects using only an App ID allow any user with the App ID to join. For access control, especially in production environments, choose the **App ID + Token** mechanism for user authentication when creating a new project. Without authentication tokens, your environment is open to anyone with access to your App ID.
### App Certificate [#app-certificate]
An App Certificate is a unique key generated by the Agora Console to secure projects through token authentication. It is required, along with the App ID, to generate a token that proves authorization between your systems and Agora's network. App Certificates are used to generate Signaling authentication tokens.
Store the App Certificate securely in your backend systems. If your App Certificate is compromised or to meet security compliance requirements, you can invalidate certificates and create new ones through the Agora Console.
### Tokens [#tokens]
A token is a dynamic key generated using the App ID, App Certificate, user ID, and expiration timestamp. Tokens authenticate and secure access to Agora's services, ensuring only authorized users can join a channel and participate in real-time communication.
Tokens are generated on your server and passed to the client for use in Signaling. The token generation process involves digitally signing the App ID, App Certificate, user ID, and expiration timestamp using a specific algorithm, preventing tampering or forgery.
During development and testing, use the Agora Console to generate temporary tokens. For production environments, implement a token server as part of your security infrastructure to control access to your channels.
### Channel [#channel]
A channel organizes users into a group and is identified by a unique channel name. Users who connect to the same channel are able to communicate with each other. A channel is created when the first user joins and ceases to exist when the last user leaves.
In Signaling, channels serve as a data transfer management mechanism for passing data between devices. Clients can subscribe to or join multiple channels simultaneously.
Signaling supports the following channel types:
| Channel Type | Main Features | Applicable use-cases |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Message | Follows the industry-standard pub/sub model. Channels do not need to be created in advance, and there is no upper limit on the number of publishers and subscribers in a channel. | Multi-device management and command exchange in the IoT industry, location tracking in smart devices, etc. |
| User | One-to-one message sending and receiving, this channel type supports the delivery receipt function. | Useful in one-on-one communication use-cases such as private chats and customer support interactions. |
| Stream | Follows the chat room model. Users need to join the channel to send and receive event notifications. Messages are managed and delivered through topics, and a single channel allows up to 1,000 users to join simultaneously. Supports channel sharing and synchronous transmission of audio and video data. | High-frequency and large concurrent data transmission or co-channel and synchronous transmission with audio and video data, such as in metaverse and cloud gaming applications. |
### User ID [#user-id]
In Signaling, UID is a unique string identifier required along with an App ID to initialize the SDK. It is used to identify the user when logging in to Signaling and throughout their session for billing and online status notifications. Users can join channels by providing just the channel name, as the UID is already associated with the user during initialization.
Users cannot simultaneously log in to Signaling using the same UID from multiple devices. If a client attempts to log in to Agora SDRTN® with a UID already present in the channel, the previously logged in client is disconnected and sent an event notification.
### Agora Console [#agora-console]
[Agora Console](https://console.agora.io/v2) is the main dashboard where you manage your Agora projects and services. Before you can use Agora's SDKs, you must first create a project in the Agora Console. See [Agora account management](./manage-agora-account.md) for
details.

Agora Console provides an intuitive interface for developers to query and manage their Agora account. After registering an Agora account, you use the Agora Console to perform the following tasks:
* Manage your account
* Create and configure Agora projects and services
* Get an App ID and the App certificate
* Generate temporary tokens for development and testing
* Manage members and roles
* Check call quality and usage
* Check bills and make payments
* Access product resources
See [Agora account management](./manage-agora-account.md) for details on how to manage all aspects of your Agora account.
Agora also provides RESTful APIs that you use to implement features such as creating a project and fetching usage numbers programmatically.
# Signaling overview (/en/realtime-media/rtm)
Agora's Signaling API enables low-latency metadata synchronization and real-time event notifications. With features like channel management, presence updates, and attribute synchronization, it enhances real-time applications with ultra-low latency and high scalability.
Extend Agora's Signaling SDKs with advanced capabilities such as stream channels, attribute storage and distribution, and integration with third-party services via webhooks to create dynamic and interactive real-time experiences.
## Start building [#start-building]
## Product Features [#product-features]
Real-time messaging that enables asynchronous pub/sub message transmission without the need for immediate response. Publishers send messages to the channel, subscribers receive messages from the channels they sign up to. Depending on your business needs, send string or binary payloads.
[Message channels](build/work-with-channels/message-channel)
A real-time data pipeline that enables the uninterrupted flow of data from one point to another without delay or latency. Depending on your business needs, send string or binary payloads
[Stream channels](build/work-with-channels/stream-channel)
Facilitate effective data stream management and communication between users in real time in Stream channels. Enable users to subscribe to, distribute, and notify users subscribed to a topic. Publishers send messages to a topic, subscribers receive messages in the topics they sign up to.
[Topics](build/work-with-channels/topics)
In online collaboration apps, enable users to see the availability of their contacts. Presence information is typically displayed as a status message or with an icon next to a user's name. It helps users determine the availability of others for communication or collaboration.
[Presence](build/manage-presence-and-metadata/presence)
Persist and managing data that is exchanged between different clients or devices in real-time. Ensure that messages are not lost or dropped during transmission and enable quick and reliable message delivery to any number of clients.
[Storage](build/manage-presence-and-metadata/storage/store-user-metadata)
Critical resource management mechanism to prevent mutual interference. Ensure that messages are processed in a specific order and prevent concurrent access to the same data. When your app accesses a resource, it can lock on that resource to prevent other clients from accessing it.
[Locks](build/manage-presence-and-metadata/storage/store-channel-metadata)
# Agora account management (/en/realtime-media/rtm/manage-agora-account)
This page shows you how to sign up for an Agora account, create a new project, and get the app ID and app certificate to generate a temporary token.
## Get started with Agora [#get-started-with-agora]
To join a Signaling session, you need an Agora App ID. This section shows you how to set up an Agora account, create an Agora project and get the required information from [Agora Console](https://console.agora.io/v2).
### Sign up for an Agora account [#sign-up-for-an-agora-account]
To use Agora products and services, create an Agora account with your email, phone number, or a third-party account.
**Sign up with email**
1. Go to the [signup page](https://sso.agora.io/en/signup).
2. Fill in the required fields.
3. Carefully read the **Terms of Service**, **Privacy Policy**, and **Acceptable Use Policy**, and tick the checkbox.
4. Click **Continue**.
5. Enter your **verification code** and click **Confirm**.
6. Follow the on-screen instructions to provide your name, company name, and phone number, set a password, and click **Continue**.
**Sign up with a third-party account**
1. On the [Agora Console](https://console.agora.io/v2) login page, select the third-party account you want to use.
2. Follow the on-screen instructions to complete verification.
3. Click **Create a new account**.
4. Carefully read the **Terms of Service**, **Privacy Policy**, and **Acceptable Use Policy**, and select the checkbox.
5. Click **Continue**.
Once you sign up successfully, your account is automatically logged in. Follow the on-screen instructions to create your first project and test out real-time communications.
For later visits, log in to [Agora Console](https://console.agora.io/v2) with your phone number, email address, or linked third-party account.
### Create an Agora project [#create-an-agora-project]
To create an Agora project, do the following:
1. In [Agora Console](https://console.agora.io/v2), open the [Projects](https://console.agora.io/v2/project-management) page.
2. Click **Create New**.
3. Follow the on-screen instructions to enter a project name and use case, and check **Secured mode: APP ID + Token (Recommended)** as the authentication mechanism.

4. Click **Submit**. You see the new project on the **Projects** page.
### Get the App ID [#get-the-app-id]
Agora automatically assigns a unique identifier to each project, called an App ID.
To copy this App ID, find your project on the [Projects](https://console.agora.io/v2/project-management) page in Agora Console, and click the copy icon in the **App ID** column.

## Enable and configure Signaling [#enable-and-configure-signaling]
Before using Signaling, you need to enable it for each app ID in Agora Console and configure its main features.
The following steps apply to the new version of Agora Console. If you are using the old one, switch to the new version by clicking **Switch to the new version** at the top of the screen.
### Enable Signaling for your project [#enable-signaling-for-your-project]
1. Select your project on the [Projects](https://console.agora.io/v2) page and click the corresponding pencil icon to configure it.

2. Go to **All features** > **Signaling** > **Basic information** and select a data center in the dropdown.

The data center setting determines the storage location for user state data, channel metadata, and user metadata for your application. Once you select a data center, this setting cannot be changed.
1. Go to **Subscriptions** > **Signaling** and subscribe to a plan.

Once subscribed, you will be able to unsubscribe from the same page.
To avoid service disruption, ensure that you are subscribed to either a paid package or the Free Package. Using Signaling without an active subscription will result in account suspension.
### Presence configuration [#presence-configuration]
To configure presence, go to **All features** > **Signaling** > **Presence Configuration** in Agora Console and set the following:
* **Max number of instant events**
Sets the maximum number of instant notifications that can be sent. After reaching this limit, Signaling switches to interval mode for sending notifications. The value range is 8–128, with a default value of 50. If your specific requirement is beyond this range, contact [technical support](mailto\:support@agora.io).
* **Timed event notification interval**
Sets how often periodic notifications are sent. The value range is 5–300 seconds, with a default value is 30 seconds.
* **Event notification debounce time**
Specifies the time interval within which no presence event is triggered if a user quickly leaves and re-joins. The default value is 2 seconds.

### Storage configuration [#storage-configuration]
To configure storage, go to **All features** > **Signaling** > **Storage Configuration** in Agora Console and toggle the following:
* **Storage** : Enable/disable storage of user and channel attributes.
* **User attribute callback**: Enable/disable callbacks for user attribute changes.
* **Channel attribute callback**: Enable/disable callbacks for channel attribute changes.
* **Distributed lock**: Enable/disable distributed locks.

### Activate stream channels [#activate-stream-channels]
The activation of Stream Channel functionality in Signaling directly depends on the activation of the 128-host feature in Real-Time Communication (RTC). To ensure proper activation:
1. Submit a formal request to [support@agora.io](mailto\:support@agora.io).
2. Specifically request activation of the 128-host feature.
3. Wait for confirmation before implementing Stream Channel features.
For further assistance or questions regarding this requirement, please contact [Agora Support](mailto\:support@agora.io).
## Security and authentication [#security-and-authentication]
Use the following features from your Agora account to implement security and authentication features in your apps.
### Get the App Certificate [#get-the-app-certificate]
When generating an authentication token on your app server, you need an App Certificate, in addition to the App ID.
To get an App Certificate, do the following:
1. On the [Projects](https://console.agora.io/v2/project-management) page, click the pencil icon to edit the project you want to use.

2. Click the copy icon under **Primary Certificate**.

### Generate temporary tokens [#generate-temporary-tokens]
To ensure communication security, best practice is to use tokens to authenticate the users who log in from your app.
To generate a temporary RTC token for use in your Video SDK projects:
1. On the [Projects](https://console.agora.io/v2/project-management) page, click the pencil icon next to your project.
2. On the **Security** panel, click **Generate Temp Token**, enter a channel name in the pop-up box and click **Generate**. Copy the generated RTC token for use in your Signaling projects.
To generate a token for other Agora products:
1. In your browser, navigate to the [Agora token builder](https://agora-token-generator-demo.vercel.app/).
2. Choose the Agora product your user wants to log in to. Fill in **App ID** and **App Certificate** with the
details of your project in Agora Console.
3. Customize the token for each user. The required fields are visible in the Agora token builder.
In Signaling, each authentication token you create is specific for a user ID in your app. You create a token for each user in the channel. When you call `login` using Signaling SDK, ensure that the UID is the same as you used to create the token.
4. Click **Generate Token**.
The token appears in Token Builder.
5. Copy the token and use it in your app.
For more information on managing other aspects of your Agora account, see [Agora console overview](./console-overview.md).
# Signaling Quickstart (/en/realtime-media/rtm/quickstart)
Use Signaling SDK to add low-latency, high-concurrency signaling and synchronization capabilities to your app.
Signaling also helps you enhance the user experience in Video Calling, Voice Calling, Interactive Live Streaming, and Broadcast Streaming applications.
This page shows you how to use the Signaling SDK to rapidly build a simple application that sends and receives messages. It shows you how to integrate the Signaling SDK in your project and implement pub/sub messaging through [Message channels](/en/realtime-media/rtm/build/work-with-channels/message-channel). To get started with stream channels, follow this guide to create a basic Signaling app and then refer to the [Stream channels](/en/realtime-media/rtm/build/work-with-channels/stream-channel) guide.
<_PlatformTabsGroup groupMode="structured" canonicalPlatform="web" platforms="["android","ios","web","flutter","linux-cpp","unity"]" showTabs="true">
<_PlatformPanel platform="android">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="android" />
## Understand the tech [#understand-the-tech]
To use Signaling features in your app, you initialize a Signaling client instance and add event listeners. To connect to Signaling, you login using an authentication token. To send a message to a message channel, you publish the message. Signaling creates a channel when a user subscribes to it. To receive messages other users publish to a channel, your app listens for events.
To create a pub/sub session for Signaling, implement the following steps in your app:

## Prerequisites [#prerequisites]
To implement the code presented on this page you need to have:
* An Agora [account](/en/realtime-media/rtm/manage-agora-account) and [project](/en/realtime-media/rtm/manage-agora-account).
* [Enabled Signaling](/en/realtime-media/rtm/manage-agora-account) in Agora Console
* [Android Studio](https://developer.android.com/studio) 4.1 or higher.
* Android SDK API Level 24 or higher.
* A mobile device that runs Android 4.1 or higher.
* Ensure that a firewall is not blocking your network communication.
Signaling 2.x is an enhanced version compared to 1.x with a wide range of new features. It follows a new pricing structure. See [Pricing](/en/realtime-media/rtm/reference/pricing) for details.
## Project setup [#project-setup]
### Create a project [#create-a-project]
1. **Create a [new project](https://developer.android.com/studio/projects/create-project)**.
1. Open Android Studio and select **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 project.
After you create a project, Android Studio automatically starts gradle sync. Ensure that the synchronization is successful before proceeding to the next step.
2. **Add network permissions**
Open the `/app/src/main/AndroidManifest.xml` file and add the following permissions before ``:
```xml
```
3. **Prevent code obfuscation**
Open the `/app/proguard-rules.pro` file and add the following line to prevent code obfuscation:
```java
-keep class io.agora.**{*;}
```
### Integrate the SDK [#integrate-the-sdk]
Use either of the following methods to integrate Signaling SDK into your project.
**Using Maven Central**
1. Open the `settings.gradle` file in the project's root directory and add the Maven Central dependency, if it doesn't already exist:
```java
repositories {
mavenCentral()
}
```
If your Android project uses [`dependencyResolutionManagement`](https://docs.gradle.org/current/userguide/declaring_repositories.html#sub\:centralized-repository-declaration), there may be differences in how you add Maven Central dependencies.
2. Add the following to the `/Gradle Scripts/build.gradle(Module: .app)` file under `dependencies` to integrate the SDK into your Android project:
* Groovy
```text
implementation 'io.agora.rtm:rtm-sdk:x.y.z'
```
To resolve integration issues when co-integrating with the RTC SDK use:
```text
implementation 'io.agora.rtm:rtm-sdk-lite:x.y.z'
```
* Kotlin
```kotlin
implementation("io.agora.rtm:rtm-sdk:x.y.z")
```
To resolve integration issues when co-integrating with the RTC SDK use:
```kotlin
implementation("io.agora.rtm:rtm-sdk-lite:x.y.z")
```
Replace `x.y.z` with the specific SDK version number, such as `2.2.8`. To get the latest version number, check the [Release notes](/en/realtime-media/rtm/reference/release-notes).
**Using CDN**
1. [Download](/en/api-reference/sdks?product=signaling\&platform=android) the latest version of Signaling SDK for Android.
2. Copy all files in the `sdk` folder of the package to the `/app/libs` folder of the project.
3. To add the SDK reference, open the project file `/Gradle Scripts/build.gradle(Module: .app)` and add the following code:
1. Add a `ndk` node under the default `Config` node, to specify the supported architectures:
```text
Config {
// ...
ndk{
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
}
}
```
Supporting all architectures increases the app size. Best practice is to only add essential architectures based on your targets. For most use-cases, `armeabi-v7a` and `arm64-v8a` architectures are sufficient when releasing the Android app.
2. Add a `sourceSets` node under the `android` node to include the jni libraries copied to the `libs` folder:
```text
android {
// ...
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
}
```
3. To include all `jar` files in the `libs` folder as dependencies, add the following under the `dependencies` node:
```text
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
// ...
}
```
To integrate Signaling SDK version `2.2.0` or later alongside Video SDK version `4.3.0` or later, refer to [handle integration issues](/en/api-reference/faq/integration/rtm2_rtc_integration_issue).
### Create a user interface [#create-a-user-interface]
This section helps you create a simple user interface to explore the basic features of Signaling. Modify it according to your specific needs.
The demo interface consists of the following UI elements:
* Input boxes for user ID, channel name, and message
* Buttons to log in and log out of Signaling
* Buttons to subscribe and unsubscribe from a channel
* A button to publish a message
**Sample code to create the user interface**
Open the `/app/res/layout/activity_main.xml` file, and replace the contents with the following:
```xml
```
Open the `app/res/values/strings.xml` file and add following string resources:
```xml
Signaling QuickstartLoginLogoutSubscribeUnsubscribePublish messageUser IDMessage contentChannel nameyour_appidyour_token
```
## Implement Signaling [#implement-signaling]
A complete code sample that implements the basic features of Signaling is presented here for your reference. To use the sample code, copy the following lines into the `/app/src/main/java/com/example//MainActivity` file and replace `` in `package com.example.` with the name of your project.
**Complete sample code for Signaling**
Java
Kotlin
```java
package com.example.;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import io.agora.rtm.*;
public class JavaActivity extends AppCompatActivity {
private EditText etUserId;
private EditText etChannelName;
private EditText etMessageContent;
private TextView mMessageHistory;
private RtmClient mRtmClient;
private final RtmEventListener eventListener = new RtmEventListener() {
@Override
public void onMessageEvent(MessageEvent event) {
String text = "Message received from " + event.getPublisherId()
+ ", Message: " + event.getMessage().getData();
writeToMessageHistory(text);
}
@Override
public void onPresenceEvent(PresenceEvent event) {
String text = "Received presence event, user: " + event.getPublisherId()
+ ", Event: " + event.getEventType();
writeToMessageHistory(text);
}
@Override
public void onLinkStateEvent(LinkStateEvent event) {
String text = "Connection state changed to " + event.getCurrentState()
+ ", Reason: " + event.getReason();
writeToMessageHistory(text);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void onClickLogin(View v) {
etUserId = findViewById(R.id.uid);
String userId = etUserId.getText().toString();
String token = getString(R.string.token);
if (createClient(userId)) {
login(token);
}
}
public void onClickLogout(View v) {
logout();
}
public void onClickSubscribe(View v) {
etChannelName = findViewById(R.id.channel_name);
String channelName = etChannelName.getText().toString();
subscribe(channelName);
}
public void onClickUnsubscribe(View v) {
etChannelName = findViewById(R.id.channel_name);
String channelName = etChannelName.getText().toString();
unsubscribe(channelName);
}
public void onClickSendChannelMsg(View v) {
etChannelName = findViewById(R.id.channel_name);
String channelName = etChannelName.getText().toString();
etMessageContent = findViewById(R.id.msg_box);
String message = etMessageContent.getText().toString();
publishMessage(channelName, message);
}
private boolean createClient(String userId) {
if (userId.isEmpty()) {
showToast("Invalid userId");
return false;
}
try {
RtmConfig config = new RtmConfig.Builder(getString(R.string.app_id), userId)
.eventListener(eventListener)
.build();
mRtmClient = RtmClient.create(config);
return true;
} catch (Exception e) {
showToast("Error creating RTM client.");
return false;
}
}
private void login(String token) {
if (mRtmClient == null) {
showToast("RTM client is null");
return;
}
mRtmClient.login(token, new ResultCallback<>() {
@Override
public void onSuccess(Void responseInfo) {
writeToMessageHistory("Successfully logged in to Signaling!");
}
@Override
public void onFailure(ErrorInfo errorInfo) {
writeToMessageHistory("Failed to log in to Signaling: " + errorInfo);
}
});
}
private void logout() {
if (mRtmClient == null) {
showToast("RTM client is null");
return;
}
mRtmClient.logout(new ResultCallback<>() {
@Override
public void onSuccess(Void responseInfo) {
writeToMessageHistory("Successfully logged out.");
}
@Override
public void onFailure(ErrorInfo errorInfo) {
writeToMessageHistory("Failed to log out: " + errorInfo);
}
});
}
private void subscribe(String channelName) {
if (mRtmClient == null) {
showToast("RTM client is null");
return;
}
SubscribeOptions options = new SubscribeOptions();
options.setWithMessage(true);
mRtmClient.subscribe(channelName, options, new ResultCallback<>() {
@Override
public void onSuccess(Void responseInfo) {
writeToMessageHistory("Successfully subscribed to the channel!");
}
@Override
public void onFailure(ErrorInfo errorInfo) {
writeToMessageHistory("Failed to subscribe to the channel: " + errorInfo);
}
});
}
private void unsubscribe(String channelName) {
if (mRtmClient == null) {
showToast("RTM client is null");
return;
}
mRtmClient.unsubscribe(channelName, new ResultCallback<>() {
@Override
public void onSuccess(Void responseInfo) {
writeToMessageHistory("Successfully unsubscribed from the channel!");
}
@Override
public void onFailure(ErrorInfo errorInfo) {
writeToMessageHistory("Failed to unsubscribe from the channel: " + errorInfo);
}
});
}
private void publishMessage(String channelName, String message) {
if (mRtmClient == null) {
showToast("RTM client is null");
return;
}
PublishOptions options = new PublishOptions();
options.setCustomType("");
mRtmClient.publish(channelName, message, options, new ResultCallback<>() {
@Override
public void onSuccess(Void responseInfo) {
writeToMessageHistory("Message sent to channel " + channelName + ": " + message);
}
@Override
public void onFailure(ErrorInfo errorInfo) {
writeToMessageHistory("Failed to send message to channel " + channelName + ": " + errorInfo);
}
});
}
private void writeToMessageHistory(String record) {
if (mMessageHistory == null) {
mMessageHistory = findViewById(R.id.message_history);
}
mMessageHistory.append("- " + record + "
");
}
private void showToast(String text) {
Toast.makeText(this, text, Toast.LENGTH_SHORT).show();
}
}
```
```kotlin
package com.example.
import android.content.pm.ActivityInfo
import android.os.Bundle
import android.view.View
import android.widget.EditText
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import io.agora.rtm.*
class MainActivity : AppCompatActivity() {
private lateinit var etUserId: EditText
private lateinit var etChannelName: EditText
private lateinit var etMessageContent: EditText
private var mRtmClient: RtmClient? = null
private lateinit var mMessageHistory: TextView
private val eventListener = object : RtmEventListener {
override fun onMessageEvent(event: MessageEvent) {
val text = "Message received from \${event.publisherId}, Message: \${event.message.data}"
writeToMessageHistory(text)
}
override fun onPresenceEvent(event: PresenceEvent) {
val text = "Received presence event, user: \${event.publisherId}, Event: \${event.eventType}"
writeToMessageHistory(text)
}
override fun onLinkStateEvent(event: LinkStateEvent) {
val text = "Connection state changed to \${event.currentState}, Reason: \${event.reason}"
writeToMessageHistory(text)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
}
fun onClickLogin(v: View) {
etUserId = findViewById(R.id.uid)
val userId = etUserId.text.toString()
val token = getString(R.string.token)
if (createClient(userId)) {
login(token)
}
}
fun onClickLogout(v: View) {
logout()
}
fun onClickSubscribe(v: View) {
etChannelName = findViewById(R.id.channel_name)
val channelName = etChannelName.text.toString()
subscribe(channelName)
}
fun onClickUnsubscribe(v: View) {
etChannelName = findViewById(R.id.channel_name)
val channelName = etChannelName.text.toString()
unsubscribe(channelName)
}
fun onClickSendChannelMsg(v: View) {
etChannelName = findViewById(R.id.channel_name)
val channelName = etChannelName.text.toString()
etMessageContent = findViewById(R.id.msg_box)
val message = etMessageContent.text.toString()
publishMessage(channelName, message)
}
private fun createClient(userId: String): Boolean {
if (userId.isEmpty()) {
showToast("Invalid userId")
return false
}
return try {
val config = RtmConfig.Builder(getString(R.string.app_id), userId)
.eventListener(eventListener)
.build()
mRtmClient = RtmClient.create(config)
true
} catch (e: Exception) {
showToast("Error creating RTM client.")
false
}
}
private fun login(token: String) {
mRtmClient?.login(token, object : ResultCallback {
override fun onSuccess(responseInfo: Void?) {
writeToMessageHistory("Successfully logged in to Signaling!")
}
override fun onFailure(errorInfo: ErrorInfo) {
writeToMessageHistory("Failed to log in to Signaling: $errorInfo")
}
}) ?: showToast("RTM client is null")
}
private fun logout() {
mRtmClient?.logout(object : ResultCallback {
override fun onSuccess(responseInfo: Void?) {
writeToMessageHistory("Successfully logged out.")
}
override fun onFailure(errorInfo: ErrorInfo) {
writeToMessageHistory("Failed to log out: $errorInfo")
}
}) ?: showToast("RTM client is null")
}
private fun subscribe(channelName: String) {
mRtmClient ?: run {
showToast("RTM client is null")
return
}
val options = SubscribeOptions().apply {
withMessage = true
}
mRtmClient!!.subscribe(channelName, options, object : ResultCallback {
override fun onSuccess(responseInfo: Void?) {
writeToMessageHistory("Successfully subscribed to the channel!")
}
override fun onFailure(errorInfo: ErrorInfo) {
writeToMessageHistory("Failed to subscribe to the channel: $errorInfo")
}
})
}
private fun unsubscribe(channelName: String) {
mRtmClient ?: run {
showToast("RTM client is null")
return
}
mRtmClient!!.unsubscribe(channelName, object : ResultCallback {
override fun onSuccess(responseInfo: Void?) {
writeToMessageHistory("Successfully unsubscribed from the channel!")
}
override fun onFailure(errorInfo: ErrorInfo) {
writeToMessageHistory("Failed to unsubscribe from the channel: $errorInfo")
}
})
}
private fun publishMessage(channelName: String, message: String) {
mRtmClient ?: run {
showToast("RTM client is null")
return
}
val options = PublishOptions().apply {
customType = ""
}
mRtmClient!!.publish(channelName, message, options, object : ResultCallback {
override fun onSuccess(responseInfo: Void?) {
writeToMessageHistory("Message sent to channel $channelName: $message")
}
override fun onFailure(errorInfo: ErrorInfo) {
writeToMessageHistory("Failed to send message to channel $channelName: $errorInfo")
}
})
}
private fun writeToMessageHistory(record: String) {
mMessageHistory = findViewById(R.id.message_history)
mMessageHistory.append("- $record
")
}
private fun showToast(text: String) {
Toast.makeText(this, text, Toast.LENGTH_SHORT).show()
}
}
```
Follow the implementation steps to understand the core API calls in the sample code or use the snippets in your own code.
### Import Agora classes [#import-agora-classes]
To use Signaling APIs in your project, import the relevant Agora classes and interfaces:
Java
Kotlin
```java
import io.agora.rtm.*;
```
```kotlin
import io.agora.rtm.*
```
### Initialize the Signaling engine [#initialize-the-signaling-engine]
Before calling any other Signaling SDK API, initialize an `RtmClient` object instance.
Java
Kotlin
```java
private boolean createClient(String userId) {
if (userId.isEmpty()) {
showToast("Invalid userId");
return false;
}
try {
RtmConfig config = new RtmConfig.Builder(getString(R.string.app_id), userId)
.eventListener(eventListener)
.build();
mRtmClient = RtmClient.create(config);
return true;
} catch (Exception e) {
showToast("Error creating RTM client.");
return false;
}
}
```
```kotlin
private fun createClient(userId: String): Boolean {
if (userId.isEmpty()) {
showToast("Invalid userId")
return false
}
return try {
// Create a configuration object
val config = RtmConfig.Builder(getString(R.string.app_id), userId)
.eventListener(eventListener)
.build()
// Use the configuration object to instantiate the engine
mRtmClient = RtmClient.create(config)
true
} catch (e: Exception) {
showToast("Error creating RTM client.")
false
}
}
```
### Add an event listener [#add-an-event-listener]
The event listener enables you to implement the processing logic in response to Signaling events. Use the following code to handle event notifications or display received messages:
Java
Kotlin
```java
private RtmEventListener eventListener = new RtmEventListener() {
@Override
public void onMessageEvent(MessageEvent event) {
String text = "Message received from " + event.getPublisherId()
+ " Message: " + event.getMessage().getData();
writeToMessageHistory(text);
}
@Override
public void onPresenceEvent(PresenceEvent event) {
String text = "Received presence event, user: " + event.getPublisherId()
+ " Event: " + event.getEventType();
writeToMessageHistory(text);
}
@Override
public void onLinkStateEvent(LinkStateEvent event) {
String text = "Connection state changed to " + event.getCurrentState()
+ ", Reason: " + event.getReason();
writeToMessageHistory(text);
}
};
```
```kotlin
private val eventListener = object : RtmEventListener {
override fun onMessageEvent(event: MessageEvent) {
val text = "Message received from \${event.publisherId} Message: \${event.message.data}"
writeToMessageHistory(text)
}
override fun onPresenceEvent(event: PresenceEvent) {
val text = "Received presence event, user: \${event.publisherId} event: \${event.eventType}"
writeToMessageHistory(text)
}
override fun onLinkStateEvent(event: LinkStateEvent) {
val text = "Connection state changed to \${event.currentState}, Reason: \${event.reason}"
writeToMessageHistory(text)
}
}
```
### Log in to Signaling [#log-in-to-signaling]
To connect to Signaling and access Signaling network resources, such as sending messages, and subscribing to channels, call `login`.
During a login operation, the client attempts to establish a connection with Signaling. Once the connection is established, the client transmits heartbeat information to the Signaling server at fixed intervals to keep the client active until the client actively logs out or is disconnected. The connection is interrupted when timeout occurs. During this period, users may freely access the Signaling network resources subject to their own permissions and usage restrictions.
Java
Kotlin
```java
private void login(String token) {
if (mRtmClient == null) {
showToast("RTM client is null");
return;
}
mRtmClient.login(token, new ResultCallback<>() {
@Override
public void onSuccess(Void responseInfo) {
writeToMessageHistory("Successfully logged in to Signaling!");
}
@Override
public void onFailure(ErrorInfo errorInfo) {
writeToMessageHistory("Failed to log in to Signaling: " + errorInfo);
}
});
}
```
```kotlin
private fun login(token: String) {
mRtmClient?.login(token, object : ResultCallback {
override fun onSuccess(responseInfo: Void?) {
writeToMessageHistory("Successfully logged in to Signaling!")
}
override fun onFailure(errorInfo: ErrorInfo) {
writeToMessageHistory("Failed to log in to Signaling: $errorInfo")
}
}) ?: showToast("RTM client is null")
}
```
To confirm that login is successful, use the `login` return value, or listen to the `onLinkStateEvent` event notification which provides the error code and reason for the login failure. When performing a login operation, the client's network connection state is `CONNECTING`. After a successful login, the state is updated to `CONNECTED`.
Best practice
To continuously monitor the network connection state of the client, best practice is to continue to listen for `onLinkStateEvent` notifications throughout the life cycle of the application. For further details, see [Event listeners](/en/realtime-media/rtm/build/send-and-receive-messages/add-event-listener).
After a user successfully logs into Signaling, the application's PCU increases, which affects your billing data.
### Publish a message [#publish-a-message]
To distribute a message to all subscribers of a message channel, call `publish`. The following code sends a string type message.
Java
Kotlin
```java
private void publishMessage(String channelName, String message) {
if (mRtmClient == null) {
showToast("RTM client is null");
return;
}
PublishOptions options = new PublishOptions();
options.setCustomType("");
mRtmClient.publish(channelName, message, options, new ResultCallback<>() {
@Override
public void onSuccess(Void responseInfo) {
writeToMessageHistory("Message sent to channel " + channelName + ": " + message);
}
@Override
public void onFailure(ErrorInfo errorInfo) {
writeToMessageHistory("Failed to send message to channel " + channelName + ": " + errorInfo);
}
});
}
```
```kotlin
private fun publishMessage(channelName: String, message: String) {
mRtmClient ?: run {
showToast("RTM client is null")
return
}
val options = PublishOptions().apply {
customType = ""
}
mRtmClient!!.publish(channelName, message, options, object : ResultCallback {
override fun onSuccess(responseInfo: Void?) {
writeToMessageHistory("Message sent to channel $channelName: $message")
}
override fun onFailure(errorInfo: ErrorInfo) {
writeToMessageHistory("Failed to send message to channel $channelName: $errorInfo")
}
})
}
```
Before calling `publish` to send a message, serialize the message payload as a string.
### Subscribe and unsubscribe [#subscribe-and-unsubscribe]
To subscribe to a channel, call `subscribe`. When you subscribe to a channel, you receive all messages published to the channel.
Java
Kotlin
```java
private void subscribe(String channelName) {
if (mRtmClient == null) {
showToast("RTM client is null");
return;
}
SubscribeOptions options = new SubscribeOptions();
options.setWithMessage(true);
mRtmClient.subscribe(channelName, options, new ResultCallback<>() {
@Override
public void onSuccess(Void responseInfo) {
writeToMessageHistory("Successfully subscribed to the channel!");
}
@Override
public void onFailure(ErrorInfo errorInfo) {
writeToMessageHistory("Failed to subscribe to the channel: " + errorInfo);
}
});
}
```
```kotlin
private fun subscribe(channelName: String) {
mRtmClient ?: run {
showToast("RTM client is null")
return
}
val options = SubscribeOptions().apply {
withMessage = true
}
mRtmClient!!.subscribe(channelName, options, object : ResultCallback {
override fun onSuccess(responseInfo: Void?) {
writeToMessageHistory("Successfully subscribed to the channel!")
}
override fun onFailure(errorInfo: ErrorInfo) {
writeToMessageHistory("Failed to subscribe to the channel: $errorInfo")
}
})
}
```
When you no longer need to receive messages from a channel, call `unsubscribe` to unsubscribe from the channel:
Java
Kotlin
```java
private void unsubscribe(String channelName) {
if (mRtmClient == null) {
showToast("RTM client is null");
return;
}
mRtmClient.unsubscribe(channelName, new ResultCallback<>() {
@Override
public void onSuccess(Void responseInfo) {
writeToMessageHistory("Successfully unsubscribed from the channel!");
}
@Override
public void onFailure(ErrorInfo errorInfo) {
writeToMessageHistory("Failed to unsubscribe from the channel: " + errorInfo);
}
});
}
```
```kotlin
private fun unsubscribe(channelName: String) {
mRtmClient ?: run {
showToast("RTM client is null")
return
}
mRtmClient!!.unsubscribe(channelName, object : ResultCallback {
override fun onSuccess(responseInfo: Void?) {
writeToMessageHistory("Successfully unsubscribed from the channel!")
}
override fun onFailure(errorInfo: ErrorInfo) {
writeToMessageHistory("Failed to unsubscribe from the channel: $errorInfo")
}
})
}
```
For more information about subscribing and sending messages, see [Message channels](/en/realtime-media/rtm/build/work-with-channels/message-channel) and [Stream channels](/en/realtime-media/rtm/build/work-with-channels/stream-channel).
### Log out of Signaling [#log-out-of-signaling]
When a user no longer needs to use Signaling, call `logout`. Logging out means closing the connection between the client and Signaling. The user is automatically logged out or unsubscribed from all message and stream channels. Other users in the channel receive an `onPresenceEvent` notification of the user leaving the channel.
Java
Kotlin
```java
private void logout() {
if (mRtmClient == null) {
showToast("RTM client is null");
return;
}
mRtmClient.logout(new ResultCallback<>() {
@Override
public void onSuccess(Void responseInfo) {
writeToMessageHistory("Successfully logged out.");
}
@Override
public void onFailure(ErrorInfo errorInfo) {
writeToMessageHistory("Failed to log out: " + errorInfo);
}
});
// Release resources
mRtmClient.release();
}
```
```kotlin
private fun logout() {
mRtmClient?.logout(object : ResultCallback {
override fun onSuccess(responseInfo: Void?) {
writeToMessageHistory("Successfully logged out.")
}
override fun onFailure(errorInfo: ErrorInfo) {
writeToMessageHistory("Failed to log out: $errorInfo")
}
}) ?: showToast("RTM client is null")
// Release resources
mRtmClient.release()
}
```
## Test Signaling [#test-signaling]
Take the following steps to test the sample code:
1. Use the [Token Builder](https://agora-token-generator-demo.vercel.app/) to generate a Signaling token:
1. Select Signaling from the Agora products dropdown.
2. Fill in your app ID and app certificate from [Agora Console](https://console.agora.io/v2). Leave the remaining fields blank.
3. Click **Generate Token**.
2. In `strings.xml`, replace the values for `app_id` and `token` with your app ID and generated token.
3. 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.
4. In Android Studio, click **Sync Project with Gradle Files** to resolve project dependencies and update the configuration.
5. After synchronization is successful, click ▶️. Android Studio starts compilation. After a few moments, the app is installed and launched on your Android device.
6. Use the device as the receiving end and perform the following operations:
1. Enter your **User ID** and click **Login**.
2. Enter the **Channel name** and click **Subscribe** .
7. On a second Android device, repeat the previous steps to install and launch the app. Use this device as the sending end.
1. Enter a different **User ID** and click **Login**.
2. Enter the same **Channel name**.
3. Type a message and click **Publish message**.
8. Swap the sending and receiving device roles and repeat the previous steps.
Congratulations! You have successfully integrated Signaling into your project.
## Reference [#reference]
This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.
### Token authentication [#token-authentication]
In this guide you retrieve a temporary token from a [Token Builder](https://agora-token-generator-demo.vercel.app/). To understand how to create an authentication server for development purposes, see [Secure authentication with tokens](./build/connect-and-authenticate/authentication-workflow).
### Sample projects [#sample-projects]
Agora provides the following open source sample projects on GitHub for your reference.
**Java**
* [Quickstart](https://github.com/AgoraIO/RTM2/tree/main/Agora-RTM2-QuickStart-Android-Java)
* [Tutorial](https://github.com/AgoraIO/RTM2/tree/main/Agora-RTM2-Tutorial-Android-Java)
**Kotlin**
* [Quickstart](https://github.com/AgoraIO/RTM2/tree/main/Agora-RTM2-QuickStart-Android-Kotlin)
* [Tutorial](https://github.com/AgoraIO/RTM2/tree/main/Agora-RTM2-Tutorial-Android-Kotlin)
Download the projects or view the source code for more detailed examples.
### API reference [#api-reference]
* [API reference](/en/api-reference/api-ref/signaling)
* [Event listeners](./build/send-and-receive-messages/add-event-listener)
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="ios">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="ios" />
## Understand the tech [#understand-the-tech-1]
To use Signaling features in your app, you initialize a Signaling client instance and add event listeners. To connect to Signaling, you login using an authentication token. To send a message to a message channel, you publish the message. Signaling creates a channel when a user subscribes to it. To receive messages other users publish to a channel, your app listens for events.
To create a pub/sub session for Signaling, implement the following steps in your app:

## Prerequisites [#prerequisites-1]
To implement the code presented on this page you need to have:
* An Agora [account](/en/realtime-media/rtm/manage-agora-account) and [project](/en/realtime-media/rtm/manage-agora-account).
* [Enabled Signaling](/en/realtime-media/rtm/manage-agora-account) in Agora Console
* Xcode 12.0 or higher.
* A device running iOS 9.0 or higher.
* Ensure that a firewall is not blocking your network communication.
Signaling 2.x is an enhanced version compared to 1.x with a wide range of new features. It follows a new pricing structure. See [Pricing](/en/realtime-media/rtm/reference/pricing) for details.
## Project setup [#project-setup-1]
### Create a project [#create-a-project-1]
In Xcode, create a Single View app under the iOS platform. Configure the project settings as follows:
* Product Name: `RtmQuickstart`
* Organization Identifier: `agora`
* User Interface: Storyboard
* Language: Choose Swift or Objective-C
### Integrate the SDK [#integrate-the-sdk-1]
Use either of the following methods to integrate Signaling SDK into your project.
**Using CDN**
1. [Download](/en/api-reference/sdks?product=signaling\&platform=ios) the latest version of Signaling SDK.
2. Copy the files in the SDK package folder `/libs/AgoraRtmKit.xcframework` to the project path.
3. Open Xcode and navigate to the **TARGETS > Project Name > General > Frameworks, Libraries, and Embedded Content** menu.
4. Click **+ > Add Other… > Add Files** to add the dynamic library `EmbedAgoraRtmKit.xcframework`, and ensure that the **Embed** property of the added dynamic library is set to **Embed & Sign**.
**Using Cocoapods**
1. To follow this procedure, ensure that you have Cocoapods installed. To install Cocoapods, refer to [Getting Started with CocoaPods](https://guides.cocoapods.org/using/getting-started.html#getting-started).
2. In the terminal, go to the project root directory and run the `pod init` command. A text file named `Podfile` is generated in the project folder .
3. Open the `Podfile` and modify the content as follows:
```ruby
platform :ios, '11.0'
target 'Your App' do
pod 'AgoraRtm', 'x.y.z'
end
```
Replace `Your App` with your target name and `x.y.z` with the specific SDK version number, such as 2.2.0. To get the latest version number, check the [Release notes](/en/realtime-media/rtm/reference/release-notes).
If you are using an SDK version earlier than `2.2.0`, change the package name to `AgoraRtm_iOS`.
4. Run the `pod install` command in the terminal to install the Signaling SDK. You see the message "Pod installation complete!".
5. After successful installation, a file with the `.xcworkspace` suffix is generated in the project folder. Open the file in Xcode for subsequent operations.
**Using Swift Package Manager**
Use the following link to integrate the SDK using Swift Package Manager (SPM):
```text
{`https://github.com/AgoraIO/AgoraRtm_Apple.git`}
```
To integrate Signaling SDK version 2.2.0 or above, and Video SDK version 4.3.0 or above at the same time, refer to [handle integration issues](/en/api-reference/faq/integration/rtm2_rtc_integration_issue).
### Create a user interface [#create-a-user-interface-1]
This section helps you create a simple user interface to explore the basic features of Signaling. Modify it according to your specific needs.
The demo interface consists of the following UI elements:
* Input boxes for user ID, channel name, and message
* Buttons to log in and log out of Signaling
* Buttons to subscribe and unsubscribe from a channel
* A button to publish a message
**Sample code to create the user interface**
Swift
Objective-C
```swift
// User Interface
struct ContentView: View {
@StateObject private var viewModel = ChatViewModel()
var body: some View {
VStack {
TextField("Enter username", text: $viewModel.username)
.padding()
.textFieldStyle(RoundedBorderTextFieldStyle())
.font(.title)
HStack {
Button("Login") {
viewModel.login()
}
.buttonStyle(LoginButtonStyle())
Button("Logout") {
viewModel.logout()
}
.buttonStyle(LogoutButtonStyle())
}
TextField("Channel name", text: $viewModel.channel)
.padding()
.textFieldStyle(RoundedBorderTextFieldStyle())
.font(.title)
HStack {
Button("Subscribe") {
viewModel.subscribe()
}
.buttonStyle(SubscribeButtonStyle())
Button("Unsubscribe") {
viewModel.unsubscribe()
}
.buttonStyle(UnsubscribeButtonStyle())
}
TextField("Enter message", text: $viewModel.message)
.padding()
.textFieldStyle(RoundedBorderTextFieldStyle())
.font(.title)
Button("Send") {
viewModel.sendMessage()
}
.buttonStyle(SendButtonStyle())
// Display messages
ScrollViewReader { scrollProxy in
List(viewModel.messages) { message in
Text(message.content)
.id(message.id)
}
.onChange(of: viewModel.messages) { _ in
if let lastMessage = viewModel.messages.last {
withAnimation {
scrollProxy.scrollTo(lastMessage.id, anchor: .bottom)
}
}
}
}
.padding()
}
.padding()
}
}
// Preview
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
```
Open `Main.storyboard` using **Code View** and replace the file contents with the following:
```xml
```
Open the file `ViewController.h` and replace the contents with the following:
```objc
#import
#import
@interface ViewController : UIViewController
// Buttons
@property (weak, nonatomic) IBOutlet UIButton *LoginButton;
@property (weak, nonatomic) IBOutlet UIButton *LogoutButton;
@property (weak, nonatomic) IBOutlet UIButton *SubsctibeButton;
@property (weak, nonatomic) IBOutlet UIButton *UnSubscribeButton;
@property (weak, nonatomic) IBOutlet UIButton *GroupMsgButton;
// TextFields
@property (weak, nonatomic) IBOutlet UITextField *UserIDTextField;
@property (weak, nonatomic) IBOutlet UITextField *ChannelIDTextField;
@property (weak, nonatomic) IBOutlet UITextField *GroupMsgTextField;
@property (weak, nonatomic) IBOutlet UITextView *MsgTextView;
@end
```
## Implement Signaling [#implement-signaling-1]
A complete code sample that implements the basic features of Signaling is presented here for your reference. To use the sample code, open `ViewController.m` and replace the contents with the following:
**Complete sample code for real-time Signaling**
Swift
Objective-C
```swift
import SwiftUI
import Combine
import AgoraRtmKit
struct Message: Identifiable, Equatable {
let id = UUID()
let content: String
}
class ChatViewModel: NSObject, ObservableObject, AgoraRtmClientDelegate {
var appid: String = <#YOUR APPID#>
var token: String = <#YOUR TOKEN#>
var rtmKit: AgoraRtmClientKit? = nil
@Published var username: String = ""
@Published var message: String = ""
@Published var channel: String = ""
@Published var messages: [Message] = []
override init() {
super.init()
initializeEngine()
}
// Initialization the engine
func initializeEngine() {
if rtmKit == nil {
let config = AgoraRtmClientConfig(appId: appid)
rtmKit = try? AgoraRtmClientKit(config, delegate: self)
}
}
// Log in to Signaling
func login() {
if rtmKit != nil {
addToMessageList(str: "RTM already logged in! Logout first!")
return
}
let config = AgoraRtmClientConfig(appId: appid, userId: username)
rtmKit = try! AgoraRtmClientKit(config, delegate: self)
rtmKit?.login(token, userId: username, completion: { response, error in
if let error = error {
self.addToMessageList(str: "Login failed. Error code: \(error.errorCode.rawValue), reason: \(error.reason)")
} else {
self.addToMessageList(str: "\(self.username) logged in successfully.")
}
})
}
// Log out from the RTM server
func logout() {
guard let rtmKit = rtmKit else {
addToMessageList(str: "RTM is already logged out!")
return
}
rtmKit.logout()
rtmKit.destroy()
self.rtmKit = nil
addToMessageList(str: "RTM logged out!")
}
// Subscribe to a channel
func subscribe() {
guard let rtmKit = rtmKit else { return }
rtmKit.subscribe(channelName: channel, option: nil) { response, error in
if let error = error {
self.addToMessageList(str: "Subscribe to channel '\(self.channel)' failed. Error code: \(error.errorCode.rawValue), reason: \(error.reason)")
} else {
self.addToMessageList(str: "Subscribed to channel: \(self.channel) successfully.")
}
}
}
// Unsubscribe from a channel
func unsubscribe() {
guard let rtmKit = rtmKit else { return }
rtmKit.unsubscribe(channel) { response, error in
if let error = error {
self.addToMessageList(str: "Unsubscribe from channel '\(self.channel)' failed. Error code: \(error.errorCode.rawValue), reason: \(error.reason)")
} else {
self.addToMessageList(str: "Unsubscribed from channel: \(self.channel) successfully.")
}
}
}
// Publish a message
func sendMessage() {
guard !message.isEmpty, let rtmKit = rtmKit else { return }
rtmKit.publish(channelName: channel, message: message, option: nil) { response, error in
if let error = error {
self.addToMessageList(str: "Publish failed. Error code: \(error.errorCode.rawValue), reason: \(error.reason)")
} else {
self.addToMessageList(str: "Message published to channel: \(self.channel) successfully.")
}
}
message = ""
}
func addToMessageList(str: String) {
messages.append(Message(content: str))
}
// AgoraRtmClientDelegate methods
func rtmKit(_ rtmKit: AgoraRtmClientKit, didReceiveLinkStateEvent event: AgoraRtmLinkStateEvent) {
addToMessageList(str: "RTM link state changed. Current state: \(event.currentState.rawValue), previous state: \(event.previousState.rawValue)")
}
func rtmKit(_ rtmKit: AgoraRtmClientKit, didReceiveMessageEvent event: AgoraRtmMessageEvent) {
addToMessageList(str: "Message received. Channel: \(event.channelName), Publisher: \(event.publisher), Message: \(event.message.stringData!)")
}
}
// User Interface
struct ContentView: View {
@StateObject private var viewModel = ChatViewModel()
var body: some View {
VStack {
TextField("Enter username", text: $viewModel.username)
.padding()
.textFieldStyle(RoundedBorderTextFieldStyle())
.font(.title)
HStack {
Button("Login") {
viewModel.login()
}
Button("Logout") {
viewModel.logout()
}
}
TextField("Channel name", text: $viewModel.channel)
.padding()
.textFieldStyle(RoundedBorderTextFieldStyle())
.font(.title)
HStack {
Button("Subscribe") {
viewModel.subscribe()
}
Button("Unsubscribe") {
viewModel.unsubscribe()
}
}
TextField("Enter message", text: $viewModel.message)
.padding()
.textFieldStyle(RoundedBorderTextFieldStyle())
.font(.title)
Button("Send") {
viewModel.sendMessage()
}
.buttonStyle(SendButtonStyle())
// Display messages
ScrollViewReader { scrollProxy in
List(viewModel.messages) { message in
Text(message.content)
.id(message.id)
}
.onChange(of: viewModel.messages) { _ in
if let lastMessage = viewModel.messages.last {
withAnimation {
scrollProxy.scrollTo(lastMessage.id, anchor: .bottom)
}
}
}
}
.padding()
}
.padding()
}
}
// Preview
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
```
```objc
#import "ViewController.h"
@interface ViewController ()
@property(nonatomic, strong) AgoraRtmClientKit* kit;
@property NSString* appID;
@property NSString* token;
@property NSString* uid;
@property NSString* peerID;
@property NSString* channelID;
@property NSString* peerMsg;
@property NSString* channelMsg;
@property NSString* text;
@property NSMutableArray* textArray;
- (void)AddMsgToRecord:(NSString*)text;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Enter your App ID
self.appID = @"your_appid";
self.MsgTextView.textColor = UIColor.blueColor;
self.textArray = [[NSMutableArray alloc]init];
}
- (void)rtmKit:(AgoraRtmClientKit *)rtmKit didReceiveMessageEvent:(AgoraRtmMessageEvent *)event {
NSLog(@"%@", self.text);
self.text = [NSString stringWithFormat:@"receive message
From channel: %@
publisher:%@
message:%@
", event.channelName, event.publisher, event.message.stringData];
[self AddMsgToRecord:(self.text)];
}
// Add message to the UI TextView
- (void)AddMsgToRecord:(NSString*)text {
[self.textArray addObject:(self.text)];
self.MsgTextView.text = [self.textArray componentsJoinedByString:(@"
")];
}
- (IBAction)Login:(id)sender {
self.uid = self.UserIDTextField.text;
// Enter your token
self.token = @"your_token";
AgoraRtmClientConfig* rtm_config = [[AgoraRtmClientConfig alloc] initWithAppId:_appID userId:_uid];
NSError* initError = nil;
_kit = [[AgoraRtmClientKit alloc] initWithConfig:rtm_config delegate:self error:&initError];
if (initError != nil) {
self.text = [NSString stringWithFormat:@"init error %@",initError];
NSLog(@"%@", self.text);
[self AddMsgToRecord:(self.text)];
}
// Log in to the RTM server
[_kit loginByToken:self.token completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
if (errorInfo.errorCode != AgoraRtmErrorOk) {
self.text = [NSString stringWithFormat:@"Login failed for user %@. Code: %ld", self.uid, (long)errorInfo.errorCode];
NSLog(@"%@", self.text);
} else {
self.text = [NSString stringWithFormat:@"Login successful for user %@. Code: %ld", self.uid, (long)errorInfo.errorCode];
NSLog(@"%@", self.text);
}
[self AddMsgToRecord:(self.text)];
}];
}
- (IBAction)Logout:(id)sender {
if (_kit != nil) {
// Log out from the RTM server
[_kit logout:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
if (errorInfo == nil) {
self.text = [NSString stringWithFormat:@"Logout successful"];
NSLog(@"%@", self.text);
[_kit destroy];
_kit = nil;
} else {
self.text = [NSString stringWithFormat:@"Logout failed. Code: %ld", (long)errorInfo.errorCode];
NSLog(@"%@", self.text);
}
[self AddMsgToRecord:(self.text)];
}];
}
}
- (IBAction)SubscribeChannel:(id)sender {
self.channelID = self.ChannelIDTextField.text;
if (_kit != nil) {
// Subscribe to a channel
[_kit subscribeWithChannel:self.channelID option:nil completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
if (errorInfo == nil) {
self.text = [NSString stringWithFormat:@"Successfully subscribed to channel %@", self.channelID];
NSLog(@"%@", self.text);
} else {
self.text = [NSString stringWithFormat:@"Failed to subscribe to channel %@. Code: %ld", self.channelID, (long)errorInfo.errorCode];
NSLog(@"%@", self.text);
}
[self AddMsgToRecord:(self.text)];
}];
}
}
- (IBAction)UnsubscribeChannel:(id)sender {
if (_kit == nil) return;
// Unsubscribe from a channel
[_kit unsubscribeWithChannel:self.channelID completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
if (errorInfo == nil) {
self.text = [NSString stringWithFormat:@"Successfully unsubscribed from channel"];
} else {
self.text = [NSString stringWithFormat:@"Failed to unsubscribe from channel. Code: %ld", (long)errorInfo.errorCode];
}
[self AddMsgToRecord:(self.text)];
}];
}
- (IBAction)SendMessageToMessageChannel:(id)sender {
self.channelID = self.ChannelIDTextField.text;
self.channelMsg = self.GroupMsgTextField.text;
// Publish a message
[_kit publish:self.channelID message:self.channelMsg option:nil completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
if (errorInfo == nil) {
self.text = [NSString stringWithFormat:@"Message sent to channel %@ : %@", self.channelID, self.channelMsg];
} else {
self.text = [NSString stringWithFormat:@"Message failed to send to channel %@ : %@. ErrorCode: %ld", self.channelID, self.channelMsg, (long)errorInfo.errorCode];
}
[self AddMsgToRecord:(self.text)];
}];
}
@end
```
Follow the implementation steps to understand the core API calls in the sample code or use the snippets in your own code.
### Declare the variables you need [#declare-the-variables-you-need]
To connect to Signaling from your project and send messages to a channel, declare variables to hold the app ID, token, user ID, and channel name:
Swift
Objective-C
```swift
var appid: String = <#YOUR APPID#>
var token: String = <#YOUR TOKEN#>
var rtmKit: AgoraRtmClientKit? = nil
@Published var username: String = ""
@Published var message: String = ""
@Published var channel: String = ""
@Published var messages: [Message] = []
```
```objc
#import "ViewController.h"
@interface ViewController ()
@property(nonatomic, strong)AgoraRtmClientKit* kit;
@property NSString* appID;
@property NSString* token;
@property NSString* uid;
@property NSString* peerID;
@property NSString* channelID;
@property NSString* peerMsg;
@property NSString* channelMsg;
@property NSString* text;
@property NSMutableArray* textArray;
```
### Initialize the Signaling engine [#initialize-the-signaling-engine-1]
Before calling any other Signaling SDK API, initialize an `AgoraRtmClientKit` object instance.
Swift
Objective-C
```swift
// Initialization the engine
func initializeEngine() {
if rtmKit == nil {
let config = AgoraRtmClientConfig(appId: appid)
rtmKit = try? AgoraRtmClientKit(config, delegate: self)
}
}
```
```objc
self.uid = self.UserIDTextField.text;
// Set engine configuration
AgoraRtmClientConfig* rtm_config = [[AgoraRtmClientConfig alloc] initWithAppId:_appID userId:_uid];
// Initialize the engine
NSError* initError = nil;
_kit = [[AgoraRtmClientKit alloc] initWithConfig:rtm_config delegate:self error:&initError];
if (initError != nil) {
self.text = [NSString stringWithFormat:@"init error %@",initError];
NSLog(@"%@", self.text);
[self AddMsgToRecord:(self.text)];
}
```
### Add an event listener [#add-an-event-listener-1]
The event listener enables you to implement the processing logic in response to Signaling events. Use the following code to handle event notifications or display received messages:
Swift
Objective-C
```swift
// Add the event listener
func rtmKit(_ rtmKit: AgoraRtmClientKit, didReceiveLinkStateEvent event: AgoraRtmLinkStateEvent) {
addToMessageList(str: "Signaling link state change current state is: \(event.currentState.rawValue) previous state is :\(event.previousState.rawValue)")
}
func rtmKit(_ rtmKit: AgoraRtmClientKit, didReceiveMessageEvent event: AgoraRtmMessageEvent) {
addToMessageList(str: "Message received.
channel: \(event.channelName), publisher: \(event.publisher), message content: \(event.message.stringData!)")
}
```
```objc
// Add the event listener
- (void)rtmKit:(AgoraRtmClientKit *)rtmKit didReceiveMessageEvent:(AgoraRtmMessageEvent *)event {
NSLog(@"%@", self.text);
self.text = [NSString stringWithFormat:@"receive message
From channel: %@
publisher:%@
message:%@
",event.channelName,event.publisher, event.message.stringData];
[self AddMsgToRecord:(self.text)];
}
```
### Log in to Signaling [#log-in-to-signaling-1]
To connect to Signaling and access Signaling network resources, such as sending messages, and subscribing to channels, login to Signaling server.
During a login operation, the client attempts to establish a connection with Signaling. Once the connection is established, the client transmits heartbeat information to the Signaling server at fixed intervals to keep the client active until the client actively logs out or is disconnected. The connection is interrupted when timeout occurs. During this period, users may freely access the Signaling network resources subject to their own permissions and usage restrictions.
Swift
Objective-C
```swift
// Log in to the Signaling server
func login() {
if rtmKit != nil {
addToMessageList(str: "RTM already logged in! Logout first!")
return
}
let config = AgoraRtmClientConfig(appId: appid, userId: username)
rtmKit = try! AgoraRtmClientKit(config, delegate: self)
rtmKit?.login(token, userId: username, completion: { response, error in
if let error = error {
self.addToMessageList(str: "Login failed. Error code: \(error.errorCode.rawValue), reason: \(error.reason)")
} else {
self.addToMessageList(str: "\(self.username) logged in successfully.")
}
})
}
```
```objc
// Log in to signaling
[_kit loginByToken:self.token completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
if (errorInfo.errorCode != AgoraRtmErrorOk){
self.text = [NSString stringWithFormat:@"Login failed for user %@. Code: %ld",self.uid, (long)errorInfo.errorCode];
NSLog(@"%@", self.text);
} else {
NSLog(@"%@", self.text);
self.text = [NSString stringWithFormat:@"Login successful for user %@. Code: %ld",self.uid, (long)errorInfo.errorCode];
}
[self AddMsgToRecord:(self.text)];
}];
```
Use the `login` return value, or listen to the `didReceiveLinkStateEvent` event notification to confirm that login is successful or obtain the error code and cause of login failure. When performing a login operation, the client's network connection state is `connecting`. After a successful login, the state is updated to `Connected`.
Best practice
To continuously monitor the network connection state of the client, best practice is to continue to listen for `AgoraRtmLinkState` event notifications throughout the life cycle of the application. For further details, see [Event listeners](/en/realtime-media/rtm/build/send-and-receive-messages/add-event-listener).
Use the `loginByToken` return value, or listen to the `connectionChangedToState` event notification to confirm that login is successful or obtain the error code and cause of login failure. When performing a login operation, the client's network connection state is `AgoraRtmClientConnectionStateConnecting`. After a successful login, the state is updated to `AgoraRtmClientConnectionStateConnected`.
Best practice
To continuously monitor the network connection state of the client, best practice is to continue to listen for `connectionChangedToState` event notifications throughout the life cycle of the application. For further details, see [Event listeners](/en/realtime-media/rtm/build/send-and-receive-messages/add-event-listener).
After a user successfully logs into Signaling, the application's Peak Connection Usage (PCU) increases, which affects your billing data.
### Send a message [#send-a-message]
To distribute a message to all subscribers of a message channel, call `publish`. The following code sends a string type message.
Swift
Objective-C
```swift
// Publish a message
func sendMessage() {
guard !message.isEmpty, let rtmKit = rtmKit else { return }
rtmKit.publish(channelName: channel, message: message, option: nil) { response, error in
if let error = error {
self.addToMessageList(str: "Publish failed. Error code: (error.errorCode.rawValue), reason: (error.reason)")
} else {
self.addToMessageList(str: "Message published to channel: (self.channel) successfully.")
}
}
message = ""
}
```
```objc
// Send a message
[_kit publish:self.channelID message:self.channelMsg option:nil completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
if (errorInfo == nil) {
self.text = [NSString stringWithFormat:@"Message sent to channel %@ : %@", self.channelID, self.channelMsg];
} else {
self.text = [NSString stringWithFormat:@"Message failed to send to channel %@ : %@ ErrorCode: %ld", self.channelID, self.channelMsg, (long)errorInfo.errorCode];
}
[self AddMsgToRecord:(self.text)];
}];
```
### Subscribe and unsubscribe [#subscribe-and-unsubscribe-1]
To receive messages sent to a channel, subscribe to channel messages:
Swift
Objective-C
```swift
// Subscribe to a channel
func subscribe() {
guard let rtmKit = rtmKit else { return }
rtmKit.subscribe(channelName: channel, option: nil) { response, error in
if let error = error {
self.addToMessageList(str: "Subscribe to channel '(self.channel)' failed. Error code: (error.errorCode.rawValue), reason: (error.reason)")
} else {
self.addToMessageList(str: "Subscribed to channel: (self.channel) successfully.")
}
}
}
```
```objc
// Subscribe to a channel
[_kit subscribeWithChannel:self.channelID option:nil completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
if(errorInfo == nil) {
self.text = [NSString stringWithFormat:@"Successfully subscribe channel %@",self.channelID];
NSLog(@"%@", self.text);
} else {
self.text = [NSString stringWithFormat:@"Failed to subscribe channel %@ Code: %ld",self.channelID, (long)errorInfo.errorCode];
NSLog(@"%@", self.text);
}
[self AddMsgToRecord:(self.text)];
}];
```
When you no longer need to receive messages from a channel, unsubscribe from the channel:
Swift
Objective-C
```swift
// Unsubscribe from a channel
func unsubscribe() {
guard let rtmKit = rtmKit else { return }
rtmKit.unsubscribe(channel) { response, error in
if let error = error {
self.addToMessageList(str: "Unsubscribe from channel '(self.channel)' failed. Error code: (error.errorCode.rawValue), reason: (error.reason)")
} else {
self.addToMessageList(str: "Unsubscribed from channel: (self.channel) successfully.")
}
}
}
```
```objc
// Unsubscribe from a channel
[_kit unsubscribeWithChannel:self.channelID completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
if (errorInfo == nil){
self.text = [NSString stringWithFormat:@"Leave channel successful"];
} else {
self.text = [NSString stringWithFormat:@"Failed to leave channel Code: %ld", (long)errorInfo.errorCode];
}
[self AddMsgToRecord:(self.text)];
}];
```
For more information about sending and receiving messages see [Message channels](/en/realtime-media/rtm/build/work-with-channels/message-channel) and [Stream channels](/en/realtime-media/rtm/build/work-with-channels/stream-channel).
### Log out of Signaling [#log-out-of-signaling-1]
When a user no longer needs to use Signaling, call `logout`. Logging out means closing the connection between the client and Signaling. The user is automatically logged out or unsubscribed from all message and stream channels. Other users in the channel receive a `didReceivePresenceEvent` notification of the user leaving the channel.
Swift
Objective-C
```swift
// Log out from the Signaling server
func logout() {
guard let rtmKit = rtmKit else {
addToMessageList(str: "Signaling is already logged out!")
return
}
rtmKit.logout()
rtmKit.destroy()
self.rtmKit = nil
addToMessageList(str: "Signaling logged out!")
}
```
```objc
// Log out of Signaling
[_kit logout:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
if (errorInfo == nil){
self.text = [NSString stringWithFormat:@"Logout successful"];
NSLog(@"%@", self.text);
[_kit destroy];
_kit = nil;
} else {
self.text = [NSString stringWithFormat:@"Logout failed. Code: %ld",(long)errorInfo.errorCode];
NSLog(@"%@", self.text);
}
[self AddMsgToRecord:(self.text)];
}];
// Clean up
[_kit destroy];
_kit = nil;
```
## Test Signaling [#test-signaling-1]
Take the following steps to test the sample code:
1. Use the [Token Builder](https://agora-token-generator-demo.vercel.app/) to generate a Signaling token:
1. Select Signaling from the Agora products dropdown.
2. Fill in your app ID and app certificate from [Agora Console](https://console.agora.io/v2). Leave the remaining fields blank.
3. Click **Generate Token**.
2. In your code, replace the values for `appID` and `token` with your app ID and the generated token.
3. Choose the device or simulator you want to run the app on from the device selection dropdown menu in the Xcode toolbar.
4. Click **Run** in the Xcode toolbar, or press Command + R on your keyboard. Xcode builds your project and launches the app on the selected device or simulator.
5. Use the device as the receiving end and perform the following operations:
1. Enter your **User ID** and click **Login**.
2. Enter the **Channel name** and click **Subscribe** .
6. On a second device, repeat the previous steps to install and launch the app. Use this device as the sending end.
1. Enter a different **User ID** and click **Login**.
2. Enter the same **Channel name**.
3. Type a message and click **Publish message**.
7. Swap the sending and receiving device roles and repeat the previous steps.
Congratulations! You have successfully integrated Signaling into your project.
## Reference [#reference-1]
This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.
### Token authentication [#token-authentication-1]
In this guide you retrieve a temporary token from a [Token Builder](https://agora-token-generator-demo.vercel.app/). To understand how to create an authentication server for development purposes, see [Secure authentication with tokens](./build/connect-and-authenticate/authentication-workflow).
### Sample projects [#sample-projects-1]
Agora provides the following open source sample projects on GitHub for your reference.
* [Quickstart - Swift](https://github.com/AgoraIO/RTM2/tree/main/Agora-RTM2-QuickStart-iOS-Swift)
* [Quickstart - Objective-C](https://github.com/AgoraIO/RTM2/tree/main/Agora-RTM2-QuickStart-iOS-Objective-C)
### API reference [#api-reference-1]
**Swift**
* [API reference](/en/api-reference/api-ref/signaling)
* [Event listeners](./build/send-and-receive-messages/add-event-listener)
**Objective-C**
* [API reference](/en/api-reference/api-ref/signaling)
* [Event listeners](./build/send-and-receive-messages/add-event-listener)
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="web">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="web" />
## Understand the tech [#understand-the-tech-2]
To use Signaling features in your app, you initialize a Signaling client instance and add event listeners. To connect to Signaling, you login using an authentication token. To send a message to a message channel, you publish the message. Signaling creates a channel when a user subscribes to it. To receive messages other users publish to a channel, your app listens for events.
To create a pub/sub session for Signaling, implement the following steps in your app:

## Prerequisites [#prerequisites-2]
To implement the code presented on this page you need to have:
* An Agora [account](/en/realtime-media/rtm/manage-agora-account) and [project](/en/realtime-media/rtm/manage-agora-account).
* [Enabled Signaling](/en/realtime-media/rtm/manage-agora-account) in Agora Console
* A [supported browser](/en/realtime-media/rtm/reference/supported-platforms).
* A JavaScript package manager such as [npm](https://www.npmjs.com/package/npm).
* Ensure that a firewall is not blocking your network communication.
Signaling 2.x is an enhanced version compared to 1.x with a wide range of new features. It follows a new pricing structure. See [Pricing](/en/realtime-media/rtm/reference/pricing) for details.
## Project setup [#project-setup-2]
### Create a project [#create-a-project-2]
To create a folder for your project, and an `index.html` file for your app, execute the following commands in the terminal:
```bash
mkdir HelloWorld
cd HelloWorld
touch index.html
```
### Integrate the SDK [#integrate-the-sdk-2]
Use either of the following methods to integrate Signaling SDK into your project.
**Using CDN**
1. [Download](/en/api-reference/sdks?product=signaling\&platform=web) the latest version of Signaling SDK for Web.
2. Add the following code to your project to reference the SDK:
```js
```
Replace `x.y.z` with the specific SDK version number, such as 2.2.0. To get the latest version number, check the [Release notes](/en/realtime-media/rtm/reference/release-notes).
**Using npm**
1. Install the SDK using npm
```js
npm install agora-rtm-sdk
```
2. Import `AgoraRTM` from the SDK
```js
import AgoraRTM from 'agora-rtm-sdk';
```
## Implement Signaling [#implement-signaling-2]
A complete code sample that implements the basic features of Signaling is presented here for your reference. To use the sample code, copy the following lines into the `index.html` file:
**Complete sample code for Signaling**
```js
Text Display App
Hello RTM !
Send
```
Follow the implementation steps to understand the core API calls in the sample code or use the snippets in your own code.
### Initialize the Signaling engine [#initialize-the-signaling-engine-2]
Before calling any other Signaling SDK API, initialize an `RTM` object instance.
```js
try {
const rtm = new RTM(appId, userId);
} catch (status) {
console.log("Error");
console.log(status);
}
```
### Add an event listener [#add-an-event-listener-2]
The event listener enables you to implement the processing logic in response to Signaling events. Use the following code to handle event notifications or display received messages:
```js
// Message event handler.
rtm.addEventListener("message", event => {
showMessage(event.publisher, event.message);
});
// Presence event handler.
rtm.addEventListener("presence", event => {
if (event.eventType === "SNAPSHOT") {
showMessage("INFO", "I Join");
}
else {
showMessage("INFO", event.publisher + " is " + event.eventType);
}
});
// Connection state changed event handler.
rtm.addEventListener("status", event => {
// The current connection state.
const currentState = event.state;
// The reason why the connection state changes.
const changeReason = event.reason;
showMessage("INFO", JSON.stringify(event));
});
```
### Log in to Signaling [#log-in-to-signaling-2]
To connect to Signaling and access Signaling network resources, such as sending messages, and subscribing to channels, call `login`.
During a login operation, the client attempts to establish a connection with Signaling. Once the connection is established, the client transmits heartbeat information to the Signaling server at fixed intervals to keep the client active until the client actively logs out or is disconnected. The connection is interrupted when timeout occurs. During this period, users may freely access the Signaling network resources subject to their own permissions and usage restrictions.
```js
// Log in to Signaling
try {
const result = await rtm.login({ token: 'your_token' });
console.log(result);
} catch (status) {
console.log(status);
}
```
Use the `login` return value, or listen to the `status` event notification to confirm that login is successful or obtain the error code and cause of login failure. When performing a login operation, the client's network connection state is `CONNECTING`. After a successful login, the state is updated to `CONNECTED`.
Best practice
To continuously monitor the network connection state of the client, best practice is to continue to listen for `status` event notifications throughout the life cycle of the application. For further details, see [Event listeners](/en/realtime-media/rtm/build/send-and-receive-messages/add-event-listener).
After a user successfully logs into Signaling, the application's PCU increases, which affects your billing data.
### Send a message [#send-a-message-1]
To distribute a message to all subscribers of a message channel, call `publish`. The following code sends a string type message.
```js
// Send a message to a channel
const publishMessage = async (message) => {
const payload = { type: "text", message: message };
const publishMessage = JSON.stringify(payload);
const publishOptions = { channelType: 'MESSAGE'}
try {
const result = await rtm.publish(msChannelName, publishMessage, publishOptions);
showMessage(userId, publishMessage);
console.log(result);
} catch (status) {
console.log(status);
}
}
```
Before calling `publish` to send a message, serialize the message payload as a string.
### Subscribe and unsubscribe [#subscribe-and-unsubscribe-2]
To receive messages sent to a channel, call `subscribe` to subscribe to channel messages:
```js
// Subscribe to a channel
try {
const result = await rtm.subscribe(msChannelName);
console.log(result);
} catch (status) {
console.log(status);
}
```
When you no longer need to receive messages from a channel, call `unsubscribe` to unsubscribe from the channel:
```js
// Unsubscribe from a channel
try {
const result = await rtm.unsubscribe(msChannelName);
console.log(result);
} catch (status) {
console.log(status);
}
```
For more information about sending and receiving messages see [Message channels](/en/realtime-media/rtm/build/work-with-channels/message-channel) and [Stream channels](/en/realtime-media/rtm/build/work-with-channels/stream-channel).
### Log out of Signaling [#log-out-of-signaling-2]
When a user no longer needs to use Signaling, call `logout`. Logging out means closing the connection between the client and Signaling. The user is automatically logged out or unsubscribed from all message and stream channels. Other users in the channel receive a `presence` notification of the user leaving the channel.
```js
// Logout of Signaling
try {
const result = await rtm.logout();
} catch (status) {
const { operation, reason, errorCode } = status;
console.log(`${operation} failed, the error code is ${errorCode}, because of: ${reason}.`);
}
```
## Test Signaling [#test-signaling-2]
Take the following steps to test the sample code:
1. Use the [Token Builder](https://agora-token-generator-demo.vercel.app/) to generate a Signaling token:
1. Select Signaling from the Agora products dropdown.
2. Fill in your app ID and app certificate from [Agora Console](https://console.agora.io/v2). Leave the remaining fields blank.
3. Click **Generate Token**.
2. In `index.html`, replace `your_appid` and `your_token` values in the code with your project's app ID and the generated token.
3. Update the value for the `userId` with an integer value.
4. Save the file and run it in your browser.
5. Make a copy of the project. Use the same `app_id` but a different `userId`. Launch another instance of the page in a separate browser tab.
6. Type a message in the text input box in either instance and click the **Send** button. The message is displayed in the other app instance.
7. Swap the sending and receiving instances and send another message.
Congratulations! You have successfully integrated Signaling into your project.
## Reference [#reference-2]
This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.
### Token authentication [#token-authentication-2]
In this guide you retrieve a temporary token from a [Token Builder](https://agora-token-generator-demo.vercel.app/). To understand how to create an authentication server for development purposes, see [Secure authentication with tokens](./build/connect-and-authenticate/authentication-workflow).
### Sample project [#sample-project]
Explore and experiment with the [Signaling Online demo](https://digitallysavvy.github.io/RTM2/). To download and customize the source code, refer to the [GitHub repository](https://github.com/digitallysavvy/RTM2).
### API reference [#api-reference-2]
* [API reference](/en/api-reference/api-ref/signaling)
* [Event listeners](./build/send-and-receive-messages/add-event-listener)
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="flutter">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="flutter" />
## Understand the tech [#understand-the-tech-3]
To use Signaling features in your app, you initialize a Signaling client instance and add event listeners. To connect to Signaling, you login using an authentication token. To send a message to a message channel, you publish the message. Signaling creates a channel when a user subscribes to it. To receive messages other users publish to a channel, your app listens for events.
To create a pub/sub session for Signaling, implement the following steps in your app:

## Prerequisites [#prerequisites-3]
To implement the code presented on this page you need to have:
* An Agora [account](/en/realtime-media/rtm/manage-agora-account) and [project](/en/realtime-media/rtm/manage-agora-account).
* [Enabled Signaling](/en/realtime-media/rtm/manage-agora-account) in Agora Console
* [Flutter](https://docs.flutter.dev/get-started/install) 2.0.0 or higher
* Dart 2.15.1 or higher
* [Android Studio](https://developer.android.com/studio), IntelliJ, VS Code, or any other IDE that supports Flutter, see [Set up an editor](https://docs.flutter.dev/get-started/editor).
* If your target platform is iOS:
* Xcode on macOS (latest version recommended)
* A physical iOS device
* iOS version 12.0 or later
* If your target platform is Android:
* Android Studio on macOS or Windows (latest version recommended)
* An Android emulator or a physical Android device.
* If you are developing a desktop application for Windows, macOS or Linux, make sure your development device meets the [Flutter desktop development requirements](https://docs.flutter.dev/development/platform-integration/desktop).
* Ensure that a firewall is not blocking your network communication.
Signaling 2.x is an enhanced version compared to 1.x with a wide range of new features. It follows a new pricing structure. See [Pricing](/en/realtime-media/rtm/reference/pricing) for details.
## Project setup [#project-setup-3]
To create a new Flutter project, follow these steps:
1. Open a terminal and execute the following command:
```bash
flutter create sdk_quickstart
```
2. Open the project in your IDE. Add the Signaling SDK dependency to `pubspec.yaml` under `dependencies`:
```yaml
dependencies:
# Add the Signaling SDK dependency, use the latest version number
agora_rtm: ^2.2.1
```
To integrate Signaling SDK version 2.2.1 or above, and Video SDK version 4.3.0 or above at the same time, refer to [handle integration issues](/en/api-reference/faq/integration/rtm2_rtc_integration_issue).
3. To install the dependencies, open the terminal and execute the following command in the project path:
```bash
flutter pub get
```
4. To create a basic template for your project, open `main.dart`, delete the contents of the file, and paste the following code:
```dart
import 'package:agora_rtm/agora_rtm.dart';
import 'package:flutter/material.dart';
import 'dart:convert';
void main() async {
// Wait for Widgets to be initialized.
WidgetsFlutterBinding.ensureInitialized();
// Create a Signaling client instance
// Login to Signaling
// Subscribe to a channel
// Publish messages
// Unsubscribe from a channel
// Logout from Signaling
}
```
## Implement Signaling [#implement-signaling-3]
A complete code sample that implements the basic features of Signaling is presented here for your reference. To use the sample code, copy the following lines into the `lib/main.dart` file.
**Complete sample code**
```dart
import 'package:flutter/material.dart';
import 'package:agora_rtm/agora_rtm.dart';
import 'dart:convert';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
const userId = 'your_userId';
const appId = 'your_appId';
const token = 'your_token';
const channelName = 'getting-started';
late RtmClient rtmClient;
// Create a Signaling client instance
try {
// Create rtm client
final (status, client) = await RTM( appId, userId);
if (status.error == true) {
print('\${status.operation} failed due to \${status.reason}, error code: \${status.errorCode}');
} else {
rtmClient = client;
print('Initialize success!');
}
// Add events listener
rtmClient.addListener(
// Add message event handler
message: (event) {
print('received a message from channel: \${event.channelName}, channel type : \${event.channelType}');
print('message content: \${utf8.decode(event.message!)}, custom type: \${event.customType}');
},
// add link state event handler
linkState: (event) {
print('link state changed from \${event.previousState} to \${event.currentState}');
print('reason: \${event.reason}, due to operation \${event.operation}');
});
} catch (e) {
print('Initialize failed!:\${e}');
}
// Login to Signaling
try {
// login rtm service
var (status,response) = await rtmClient.login(token);
if (status.error == true) {
print('\${status.operation} failed due to \${status.reason}, error code: \${status.errorCode}');
} else {
print('login RTM success!');
}
} catch (e) {
print('Failed to login: $e');
}
// Subscribe to a channel
try {
var (status,response) = await rtmClient.subscribe(channelName);
if (status.error == true) {
print('\${status.operation} failed due to \${status.reason}, error code: \${status.errorCode}');
} else {
print('subscribe channel: \${channelName} success!');
}
} catch (e) {
print('Failed to subscribe channel: $e');
}
// Publish messages
// Send a message every second for 100 seconds
for (var i = 0; i < 100; i++) {
try {
var (status, response) = await rtmClient.publish(
channelName,
'message number : $i',
channelType: RtmChannelType.message,
customType: 'PlainText'
);
if (status.error == true ){
print('\${status.operation} failed, errorCode: \${status.errorCode}, due to \${status.reason}');
} else {
print('\${status.operation} success! message number:$i');
}
} catch (e) {
print('Failed to publish message: $e');
}
await Future.delayed(Duration(seconds: 1));
}
// Unsubscribe from a channel
try {
var (status,response) = await rtmClient.unsubscribe(channelName);
if (status.error == true) {
print('\${status.operation} failed due to \${status.reason}, error code: \${status.errorCode}');
} else {
print('unsubscribe success!');
}
} catch (e) {
print('something went wrong with logout: $e');
}
// Logout of Signaling
try {
// logout rtm service
var (status,response) = await rtmClient.logout();
if (status.error == true) {
print('\${status.operation} failed due to \${status.reason}, error code: \${status.errorCode}');
} else {
print('logout RTM success!');
}
} catch (e) {
print('something went wrong with logout: $e');
}
}
```
Follow the implementation steps to understand the core API calls in the sample code or to build the demo step-by-step.
### Initialize the Signaling engine [#initialize-the-signaling-engine-3]
Before calling any other Signaling SDK API, initialize an `RtmClient` object instance. Add the following code to the `lib/main.dart` file.
```dart
import 'package:agora_rtm/agora_rtm.dart';
import 'package:flutter/material.dart';
import 'dart:convert';
void main() async {
// Wait for Widgets to be initialized.
WidgetsFlutterBinding.ensureInitialized();
const userId = 'your_userId';
const appId = 'your_appId';
const token = 'your_token';
const channelName = 'getting-started';
late RtmClient rtmClient;
try {
// Create rtm client
final (status, client) = await RTM( appId, userId);
if (status.error == true) {
print('${status.operation} failed due to ${status.reason}, error code: ${status.errorCode}');
} else {
rtmClient = client;
print('Initialize success!');
}
// Add events listener
} catch (e) {
print('Initialize failed!:${e}');
}
}
```
### Add an event listener [#add-an-event-listener-3]
The event listener enables you to implement the processing logic in response to Signaling events. Use the following code to handle event notifications or display received messages:
```dart
// Paste the following code snippet below the "add event listener" comment
rtmClient.addListener(
// Add message event handler
message: (event) {
print('received a message from channel: ${event.channelName}, channel type : ${event.channelType}');
print('message content: ${utf8.decode(event.message!)}, custome type: ${event.customType}');
},
// Add linkState event handler
linkState: (event) {
print('link state changed from ${event.previousState} to ${event.currentState}');
print('reason: ${event.reason}, due to operation ${event.operation}');
});
```
### Log in to Signaling [#log-in-to-signaling-3]
To connect to Signaling and access Signaling network resources, such as sending messages, and subscribing to channels, call `login`.
During a login operation, the client attempts to establish a connection with Signaling. Once the connection is established, the client transmits heartbeat information to the Signaling server at fixed intervals to keep the client active until the client actively logs out or is disconnected. The connection is interrupted when timeout occurs. During this period, users may freely access the Signaling network resources subject to their own permissions and usage restrictions.
```dart
// Paste the following code snippet below "Login to Signaling" comment
try {
// Login to Signaling
var (status,response) = await rtmClient.login(token);
if (status.error == true) {
print('${status.operation} failed due to ${status.reason}, error code: ${status.errorCode}');
} else {
print('login RTM success!');
}
} catch (e) {
print('Failed to login: $e');
}
```
### Send a message [#send-a-message-2]
To distribute a message to all subscribers of a message channel, call `publish`. The following code sends a string type message every second for 100 seconds.
```dart
// Paste the following code snippet below the "Publish messages" comment
// Send a message every second for 100 seconds
for (var i = 0; i < 100; i++) {
try {
var (status, response) = await rtmClient.publish(
channelName,
'message number : $i',
channelType: RtmChannelType.message,
customType: 'PlainText'
);
if (status.error == true ){
print('${status.operation} failed, errorCode: ${status.errorCode}, due to ${status.reason}');
} else {
print('${status.operation} success! message number:$i');
}
} catch (e) {
print('Failed to publish message: $e');
}
await Future.delayed(Duration(seconds: 1));
}
```
### Subscribe and unsubscribe [#subscribe-and-unsubscribe-3]
To receive messages sent to a channel, call `subscribe` to subscribe to channel messages:
```dart
// Paste the following code snippet below the "Subscribe to a channel" comment
try {
// Subscribe to a channel
var (status,response) = await rtmClient.subscribe(channelName);
if (status.error == true) {
print('${status.operation} failed due to ${status.reason}, error code: ${status.errorCode}');
} else {
print('subscribe channel: ${channelName} success!');
}
} catch (e) {
print('Failed to subscribe channel: $e');
}
```
When you no longer need to receive messages from a channel, `unsubscribe` from the channel:
```dart
// Paste the following code snippet below the "Unsubscribe from a channel" comment
try {
// Unsubscribe from a channel
var (status,response) = await rtmClient.unsubscribe(channelName);
if (status.error == true) {
print('${status.operation} failed due to ${status.reason}, error code: ${status.errorCode}');
} else {
print('unsubscribe success!');
}
} catch (e) {
print('something went wrong with logout: $e');
}
```
For more information about subscribing and sending messages, see [Message channels](/en/realtime-media/rtm/build/work-with-channels/message-channel) and [Stream channels](/en/realtime-media/rtm/build/work-with-channels/stream-channel).
### Log out of Signaling [#log-out-of-signaling-3]
When a user no longer needs to use Signaling, call `logout`. Logging out means closing the connection between the client and Signaling. The user is automatically logged out or unsubscribed from all message and stream channels. Other users in the channel receive an `onPresenceEvent` notification of the user leaving the channel.
```dart
// Paste the following code snippet below the "Log out of Signaling" comment
try {
// Log out of Signaling
var (status,response) = await rtmClient.logout();
if (status.error == true) {
print('${status.operation} failed due to ${status.reason}, error code: ${status.errorCode}');
} else {
print('logout RTM success!');
}
} catch (e) {
print('something went wrong with logout: $e');
}
```
## Test Signaling [#test-signaling-3]
Take the following steps to test the sample code:
1. Use the [Token Builder](https://agora-token-generator-demo.vercel.app/) to generate a Signaling token:
1. Select Signaling from the Agora products dropdown.
2. Fill in your app ID and app certificate from [Agora Console](https://console.agora.io/v2). Leave the remaining fields blank.
3. Click **Generate Token**.
2. In your code, replace `your_appId` with your app ID from Agora Console, `your_userId` with a numeric string and `your_token` with the generated token.
3. Save the project.
4. Connect the target device to your computer, or open the device emulator.
5. In the terminal, execute the following command in the project path to run the sample project on the target device:
```bash
flutter run
```
You see the following output in the terminal:
```text
flutter: publish success! message number:0
flutter: publish success! message number:1
flutter: publish success! message number:2
flutter: publish success! message number:3
```
6. Run a second instance of the project on another device or emulator using a different `userId`.
You see the following notifications for messages sent from the first app instance:
```text
flutter: received a message from channel: getting-started, channel type : RtmChannelType.message
flutter: message content: message number : 0, custom type: PlainText
flutter: received a message from channel: getting-started, channel type : RtmChannelType.message
flutter: message content: message number : 1, custom type: PlainText
```
## Reference [#reference-3]
This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.
### Token authentication [#token-authentication-3]
In this guide you retrieve a temporary token from a [Token Builder](https://agora-token-generator-demo.vercel.app/). To understand how to create an authentication server for development purposes, see [Secure authentication with tokens](./build/connect-and-authenticate/authentication-workflow).
### Sample project [#sample-project-1]
Agora provides an open source [sample project](https://github.com/AgoraIO/RTM2/tree/main/Agora-RTM2-QuickStart-Flutter) on GitHub for your reference. Download it or view the source code for a more detailed example.
### API reference [#api-reference-3]
* [API reference](/en/api-reference/api-ref/signaling)
* [Event listeners](./build/send-and-receive-messages/add-event-listener)
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="linux-cpp">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="linux-cpp" />
## Understand the tech [#understand-the-tech-4]
To use Signaling features in your app, you initialize a Signaling client instance and add event listeners. To connect to Signaling, you login using an authentication token. To send a message to a message channel, you publish the message. Signaling creates a channel when a user subscribes to it. To receive messages other users publish to a channel, your app listens for events.
To create a pub/sub session for Signaling, implement the following steps in your app:

## Prerequisites [#prerequisites-4]
To implement the code presented on this page you need to have:
* An Agora [account](/en/realtime-media/rtm/manage-agora-account) and [project](/en/realtime-media/rtm/manage-agora-account).
* [Enabled Signaling](/en/realtime-media/rtm/manage-agora-account) in Agora Console
- A device running Linux Ubuntu 14.04 or above; 18.04+ is recommended.
- At least 2 GB of memory.
- `cmake` 3.6.0 or above.
* Ensure that a firewall is not blocking your network communication.
Signaling 2.x is an enhanced version compared to 1.x with a wide range of new features. It follows a new pricing structure. See [Pricing](/en/realtime-media/rtm/reference/pricing) for details.
## Project setup [#project-setup-4]
### Create a project [#create-a-project-3]
Create the following folder structure for your project:
```bash
RTMProject/
├── include/
└── lib/
```
### Integrate the SDK [#integrate-the-sdk-3]
To integrate the Linux C++ Signaling SDK into your project:
1. [Download](/en/api-reference/sdks?product=signaling\&platform=linux) the latest version of the SDK and unzip it.
To integrate Signaling SDK 2.2.1 or later, and Video SDK 4.3.0 or later in the same project, refer to [handle integration issues](/en/api-reference/faq/integration/rtm2_rtc_integration_issue).
2. Copy the files in the SDK package to the project folder as follows:
* Copy the `/rtm/sdk/libagora_rtm_sdk.so` file to the project's `lib` folder.
* Copy all `*.h` files in `/rtm/sdk/high_level_api/include` to the project's `include` folder.
3. To use `CMake` to compile the project, create the following files in the `RTMProject` folder:
* `CMakeLists.txt`
```bash
# CMakeLists.txt
cmake_minimum_required (VERSION 2.8)
project(RTMProject)
set(TARGET_NAME RTMQuickStart)
set(SOURCES RTMQuickStart.cpp)
set(HEADERS)
set(TARGET_BUILD_TYPE "Debug")
set(CMAKE_CXX_FLAGS "-fPIC -O2 -g -std=c++11 -msse2")
include_directories(${CMAKE_SOURCE_DIR}/include)
link_directories(${CMAKE_SOURCE_DIR}/lib)
add_executable(${TARGET_NAME} ${SOURCES} ${HEADERS})
target_link_libraries(${TARGET_NAME} agora_rtm_sdk pthread)
```
* `clean_build.sh`
```bash
# clean_build.sh
rm -fr build
mkdir -p build
cd build
cmake ..
make
cd ..
```
## Implement Signaling [#implement-signaling-4]
A complete code sample that implements the basic features of Signaling is presented here for your reference. To use the sample code, create a new file named `RTMQuickStart.cpp` in the `RTMProject` folder and add the following code:
**Complete sample code for Signaling**
```cpp
#include
#include
#include
#include
//#include
#include "IAgoraRtmClient.h"
// Pass in your App ID and token
const std::string APP_ID = "";
const std::string TOKEN = "";
// Terminal color codes for UBUNTU/LINUX
#define RESET "\\033\[0m"
#define BLACK "\\033\[30m" /* Black */
#define RED "\\033\[31m" /* Red */
#define GREEN "\\033\[32m" /* Green */
#define YELLOW "\\033\[33m" /* Yellow */
#define BLUE "\\033\[34m" /* Blue */
#define MAGENTA "\\033\[35m" /* Magenta */
#define CYAN "\\033\[36m" /* Cyan */
#define WHITE "\\033\[37m" /* White */
#define BOLDBLACK "\\033\[1m\\033\[30m" /* Bold Black */
#define BOLDRED "\\033\[1m\\033\[31m" /* Bold Red */
#define BOLDGREEN "\\033\[1m\\033\[32m" /* Bold Green */
#define BOLDYELLOW "\\033\[1m\\033\[33m" /* Bold Yellow */
#define BOLDBLUE "\\033\[1m\\033\[34m" /* Bold Blue */
#define BOLDMAGENTA "\\033\[1m\\033\[35m" /* Bold Magenta */
#define BOLDCYAN "\\033\[1m\\033\[36m" /* Bold Cyan */
#define BOLDWHITE "\\033\[1m\\033\[37m" /* Bold White */
using namespace agora::rtm;
class RtmEventHandler : public IRtmEventHandler {
public:
// Add the event listener
void onLoginResult(const uint64_t requestId, RTM_ERROR_CODE errorCode) override {
cbPrint("onLoginResult, request id: %lld, errorCode: %d", requestId, errorCode);
}
void onLogoutResult(const uint64_t requestId, RTM_ERROR_CODE errorCode) {
cbPrint("onLogoutResult, request id: %lld, errorCode: %d", requestId, errorCode);
}
void onConnectionStateChanged(const char *channelName, RTM_CONNECTION_STATE state, RTM_CONNECTION_CHANGE_REASON reason) override {
cbPrint("onConnectionStateChanged, channelName: %s, state: %d, reason: %d", channelName, state, reason);
}
void onLinkStateEvent(const LinkStateEvent& event) override {
cbPrint("onLinkStateEvent, state: %d -> %d, operation: %d, reason: %s", event.previousState, event.currentState, event.operation, event.reason);
}
void onPublishResult(const uint64_t requestId, RTM_ERROR_CODE errorCode) override {
cbPrint("onPublishResult request id: %lld result: %d", requestId, errorCode);
}
void onMessageEvent(const MessageEvent &event) override {
cbPrint("receive message from: %s, message: %s", event.publisher, event.message);
}
void onSubscribeResult(const uint64_t requestId, const char *channelName, RTM_ERROR_CODE errorCode) override {
cbPrint("onSubscribeResult: channel:%s, request id: %lld result: %d", channelName, requestId, errorCode);
}
void onUnsubscribeResult(const uint64_t requestId, const char *channelName, RTM_ERROR_CODE errorCode) override {
cbPrint("onUnsubscribeResult: channel:%s, request id: %lld result: %d", channelName, requestId, errorCode);
}
private:
void cbPrint(const char* fmt, ...) {
printf("\\x1b[32m<< RTM async callback: ");
va_list args;
va_start(args, fmt);
vprintf(fmt, args);
va_end(args);
printf(" >>\\x1b[0m\\n");
}
};
class RtmDemo {
public:
RtmDemo()
: eventHandler_(new RtmEventHandler()),
rtmClient_(nullptr) { Init(); }
void Init() {
std::cout << YELLOW << "Please enter userID (literal \\"null\\" or starting"
<< "with space is not allowed, no more than 64 characters!):" << std::endl;
std::string userID;
std::getline(std::cin, userID);
RtmConfig config;
config.appId = APP_ID.c_str();
config.userId = userID.c_str();
config.eventHandler = eventHandler_.get();
// Create an IRtmClient instance
int errorCode = 0;
rtmClient_ = createAgoraRtmClient(config, errorCode);
if (!rtmClient_ || errorCode != 0) {
std::cout << RED <<"error creating rtm service!" << std::endl;
exit(0);
}
}
void start() {
// Log in the RTM server
uint64_t requestId = 0;
rtmClient_->login(TOKEN.c_str(), requestId);
// Sample codes for the user interface
mainMeun();
std::cout << YELLOW << "quit ? yes/no" << std::endl;
std::string input;
std::getline(std::cin, input);
if (input.compare("yes") == 0) {
exit(0);
}
}
// Log out from the RTM server
void logout() {
uint64_t requestId = 0;
rtmClient_->logout(requestId);
}
// Subscribe to a channel
void subscribeChannel(const std::string& channelName) {
uint64_t requestId = 0;
rtmClient_->subscribe(channelName.c_str(), SubscribeOptions(), requestId);
}
// Unsubscribe from a channel
void unsubscribeChannel(const std::string& channelName) {
uint64_t requestId = 0;
rtmClient_->unsubscribe(channelName.c_str(), requestId);
}
// Publish a message
void publishMessage(const std::string& channelName, const std::string& message) {
PublishOptions options;
options.messageType = RTM_MESSAGE_TYPE_STRING;
uint64_t requestId;
rtmClient_->publish(channelName.c_str(), message.c_str(), message.size(), options, requestId);
}
// Sample codes for the user interface
void mainMeun() {
bool quit = false;
while (!quit) {
std::cout << WHITE
<< "1: subscribe channel\\n"
<< "2: unsubscribe channel\\n"
<< "3: publish message\\n"
<< "4: logout" << std::endl;
std::cout << YELLOW <<"please input your choice: " << std::endl;
std::string input;
std::getline(std::cin, input);
int32_t choice = 0;
try {
choice = std::stoi(input);
} catch(...) {
std::cout < eventHandler_;
IRtmClient* rtmClient_;
};
int main(int argc, const char * argv[]) {
RtmClient messaging;
messaging.login();
return 0;
}
```
Follow the implementation steps to understand the core API calls in the sample code or use the snippets in your own code.
### Include the header file [#include-the-header-file]
To use Signaling APIs in your project, include the `IAgoraRtmClient` header file:
```cpp
#include "IAgoraRtmClient.h"
```
### Initialize the Signaling engine [#initialize-the-signaling-engine-4]
Before calling any other Signaling SDK API, initialize an `IRtmClient` object instance.
```cpp
std::string userID;
std::getline(std::cin, userID);
RtmConfig config;
config.appId = APP_ID.c_str();
config.userId = userID.c_str();
config.eventHandler = eventHandler_.get();
// Create an IRtmClient instance
int errorCode = 0;
rtmClient_ = createAgoraRtmClient(config, errorCode);
if (!rtmClient_ || errorCode != 0) {
std::cout << RED <<"error creating rtm service!" << std::endl;
exit(0);
}
```
### Add an event listener [#add-an-event-listener-4]
The event listener enables you to implement the processing logic in response to Signaling events. Use the following code to handle event notifications or display received messages:
```cpp
void onLoginResult(const uint64_t requestId, RTM_ERROR_CODE errorCode) override {
cbPrint("onLoginResult, request id: %lld, errorCode: %d", requestId, errorCode);
}
void onLogoutResult(const uint64_t requestId, RTM_ERROR_CODE errorCode) {
cbPrint("onLogoutResult, request id: %lld, errorCode: %d", requestId, errorCode);
}
void onConnectionStateChanged(const char *channelName, RTM_CONNECTION_STATE state, RTM_CONNECTION_CHANGE_REASON reason) override {
cbPrint("onConnectionStateChanged, channelName: %s, state: %d, reason: %d", channelName, state, reason);
}
void onLinkStateEvent(const LinkStateEvent& event) override {
cbPrint("onLinkStateEvent, state: %d -> %d, operation: %d, reason: %s", event.previousState, event.currentState, event.operation, event.reason);
}
void onPublishResult(const uint64_t requestId, RTM_ERROR_CODE errorCode) override {
cbPrint("onPublishResult request id: %lld result: %d", requestId, errorCode);
}
void onMessageEvent(const MessageEvent &event) override {
cbPrint("receive message from: %s, message: %s", event.publisher, event.message);
}
void onSubscribeResult(const uint64_t requestId, const char *channelName, RTM_ERROR_CODE errorCode) override {
cbPrint("onSubscribeResult: channel:%s, request id: %lld result: %d", channelName, requestId, errorCode);
}
void onUnsubscribeResult(const uint64_t requestId, const char *channelName, RTM_ERROR_CODE errorCode) override {
cbPrint("onUnsubscribeResult: channel:%s, request id: %lld result: %d", channelName, requestId, errorCode);
}
```
### Log in to Signaling [#log-in-to-signaling-4]
To connect to Signaling and access Signaling network resources, such as sending messages, and subscribing to channels, call `login`.
During a login operation, the client attempts to establish a connection with Signaling. Once the connection is established, the client transmits heartbeat information to the Signaling server at fixed intervals to keep the client active until the client actively logs out or is disconnected. The connection is interrupted when timeout occurs. During this period, users may freely access the Signaling network resources subject to their own permissions and usage restrictions.
```cpp
// Log in to Signaling
uint64_t requestId = 0;
rtmClient_->login(TOKEN.c_str(), requestId);
```
After you call this method, the SDK triggers the `onLoginResult` callback and returns the API call result.
Use the `login` return value, or listen to the `onConnectionStateChanged` event notification to confirm that login is successful or obtain the error code and cause of login failure. When performing a login operation, the client's network connection state is `RTM_CONNECTION_STATE_CONNECTING`. After a successful login, the state is updated to `RTM_CONNECTION_STATE_CONNECTED`.
Best practice
To continuously monitor the network connection state of the client, best practice is to continue to listen for `onConnectionStateChanged` event notifications throughout the life cycle of the application. For further details, see [Event listeners](/en/realtime-media/rtm/build/send-and-receive-messages/add-event-listener).
After a user successfully logs into Signaling, the application's PCU increases, which affects your billing data.
### Send a message [#send-a-message-3]
To distribute a message to all subscribers of a message channel, call `publish`. The following code sends a string type message.
```cpp
// Publish a message to the channel
void publishMessage(const std::string& channelName, const std::string& message) {
PublishOptions options;
options.messageType = RTM_MESSAGE_TYPE_STRING;
uint64_t requestId;
rtmClient_->publish(channelName.c_str(), message.c_str(), message.size(), options, requestId);
}
```
Before calling `publish` to send a message, serialize the message payload as a string.
### Subscribe and unsubscribe [#subscribe-and-unsubscribe-4]
To receive messages sent to a channel, call `subscribe` to subscribe to channel messages:
```cpp
// Subscribe to a channel
void subscribeChannel(const std::string& channelName) {
uint64_t requestId = 0;
rtmClient_->subscribe(channelName.c_str(), SubscribeOptions(), requestId);
}
```
When you no longer need to receive messages from a channel, call `unsubscribe` to unsubscribe from the channel:
```cpp
// Unsubscribe from a channel
void unsubscribeChannel(const std::string& channelName) {
uint64_t requestId = 0;
rtmClient_->unsubscribe(channelName.c_str(), requestId);
}
```
For more information about sending and receiving messages see [Message channels](/en/realtime-media/rtm/build/work-with-channels/message-channel) and [Stream channels](/en/realtime-media/rtm/build/work-with-channels/stream-channel).
### Log out of Signaling [#log-out-of-signaling-4]
When a user no longer needs to use Signaling, call `logout`. Logging out means closing the connection between the client and Signaling. The user is automatically logged out or unsubscribed from all message and stream channels. Other users in the channel receive an `onPresenceEvent` notification of the user leaving the channel.
```cpp
// Log out of Signaling
void logout() {
uint64_t requestId = 0;
rtmClient_->logout(requestId);
}
// Clean up
rtmClient_->release();
```
## Test Signaling [#test-signaling-4]
Take the following steps to test the sample code:
1. Use the [Token Builder](https://agora-token-generator-demo.vercel.app/) to generate a Signaling token:
1. Select Signaling from the Agora products dropdown.
2. Fill in your app ID and app certificate from [Agora Console](https://console.agora.io/v2). Leave the remaining fields blank.
3. Click **Generate Token**.
2. In your code, replace `` with your app ID from Agora Console and `` with the generated token. Save the project. Make sure Signaling is activated for your project in [Agora Console](https://console.agora.io/v2).
3. In the terminal, run the following command to compile the project:
```bash
sh +x clean_build.sh
```
4. Use the following commands to navigate to the `build` folder and run the app:
```bash
cd build
./RTMQuickStart
```
You see menu options to **subscribe**, **unsubscribe**, **publish**, and **logout**.
5. Follow the prompts to **subscribe** to a channel.
6. Run another instance of the app using a different user ID. Follow the prompts to **publish** a message to the same channel that you subscribed to from the other instance.
7. You see the message displayed in the instance that you used to subscribe to the channel.
Congratulations! You have successfully integrated Signaling into your project.
## Reference [#reference-4]
This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.
### Token authentication [#token-authentication-4]
In this guide you retrieve a temporary token from a [Token Builder](https://agora-token-generator-demo.vercel.app/). To understand how to create an authentication server for development purposes, see [Secure authentication with tokens](./build/connect-and-authenticate/authentication-workflow).
### Sample project [#sample-project-2]
Agora provides an open source [sample project](https://github.com/AgoraIO/RTM2/tree/main/Agora-RTM2-QuickStart-Linux-C%2B%2B) on GitHub for your reference. Download it or view the source code for a more detailed example.
### API reference [#api-reference-4]
* [API reference](/en/api-reference/api-ref/signaling)
* [Event listeners](./build/send-and-receive-messages/add-event-listener)
<_PlatformProcessedMarker close="true" />
<_PlatformPanel platform="unity">
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="unity" />
## Understand the tech [#understand-the-tech-5]
To use Signaling features in your game, you initialize a Signaling client instance and add event listeners. To connect to Signaling, you login using an authentication token. To send a message to a message channel, you publish the message. Signaling creates a channel when a user subscribes to it. To receive messages other users publish to a channel, your game listens for events.
To create a pub/sub session for Signaling, implement the following steps in your game:

## Prerequisites [#prerequisites-5]
To implement the code presented on this page you need to have:
* An Agora [account](/en/realtime-media/rtm/manage-agora-account) and [project](/en/realtime-media/rtm/manage-agora-account).
* [Enabled Signaling](/en/realtime-media/rtm/manage-agora-account) in Agora Console
* [Unity Hub](https://unity.com/download)
* [Unity Editor 2018.4.0 or higher](https://unity.com/releases/editor/archive)
* Operating System and compiler requirements:
| Development Platform | Operating system version | Compiler version |
| -------------------- | ------------------------ | ------------------------------------- |
| Android | Android 4.1 or above | Android Studio 3.0 or above |
| iOS | iOS 11.0 or above | Xcode 9.0 or above |
| macOS | macOS 10.10 or above | Xcode 9.0 or above |
| Windows | Windows 7 or above | Microsoft Visual Studio 2017 or above |
* Ensure that a firewall is not blocking your network communication.
Signaling 2.x is an enhanced version compared to 1.x with a wide range of new features. It follows a new pricing structure. See [Pricing](/en/realtime-media/rtm/reference/pricing) for details.
## Project setup [#project-setup-5]
### Create a project [#create-a-project-4]
1. In Unity Hub, select **Projects**, then click **New Project**.
2. In **All templates**, select **3D**. Set the **Project name** and **Location**, then click **Create Project**.
3. In **Projects**, double-click the project you created. Your project opens in Unity.
### Integrate the SDK [#integrate-the-sdk-4]
1. [Download](/en/api-reference/sdks?product=signaling\&platform=unity) the latest version of the Agora Signaling SDK, and unzip the contents to a local folder.
2. In Unity, click **Assets > Import Package > Custom Package**.
3. Navigate to the Signaling SDK package and click **Open**.
4. In **Import Unity Package**, click **Import**.
### Create a user interface [#create-a-user-interface-2]
To create a simple UI for verifying the basic features of Signaling, add the following elements to your interface:
```text
SampleScene
- Main Camera
- Canvas
- Image // Window background
- Txt1 // Text label, text content is Send string message to message channel
- InputField1 // Message input box
- Btn1 // Send message
- Txt2 // Text label, text content is Send binary message to stream channel
- Btn2 // Create Stream Channel
- Btn3 // Join Stream Channel
- Btn4 // Join Topic
- Btn5 // Subscribe to message publishers in Topic
- InputField2 // Message input box
- Btn6 // Send message button
- Btn7 // Release Stream Channel button
- Btn8 // Leave Stream Channel button
- Btn9 // Leave Topic button
- Btn10 // Unsubscribe from message publishers in Topic
- Pannel // Information output panel
- Txt3 // Information output text
- EventSystem
```
Your interface should look similar to the following:

## Implement Signaling [#implement-signaling-5]
A complete code sample that implements the basic features of Signaling is presented here for your reference. To use the sample code, create a new script for the project. In Unity editor, from the **Assets** menu, select **Create**, then **C# Script**. Name the new script file `HelloWorld.cs` and paste the following code in the file:
**Complete sample code for Signaling**
```csharp
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using Agora.Rtm;
public class HelloWorld : MonoBehaviour
{
[SerializeField]
private string appId = "your_appId"; // Get an App ID from Agora console
private string userId = "your_userId"; // Change the user ID to a numeric string
private string token = "your_token"; // Login token
private string rtcToken = "your_RTC_token"; // Token to join a stream channel
private string msChannelName = "SeeYou";
private string stChannelName = "Greeting";
private string topicName = "Hello_world";
private IRtmClient rtmClient;
private IStreamChannel streamChannel;
public void Awake()
{
Initialize();
}
// Initialize RTM
public async void Initialize()
{
if (string.IsNullOrEmpty(userId) || string.IsNullOrEmpty(appId))
{
ShowMessage("We need a userId and appId to initialize!");
return;
}
RtmLogConfig logConfig = new RtmLogConfig();
// Set log file path
logConfig.filePath = "myFilePath";
// Set the file size of the agore.log file
logConfig.fileSizeInKB = 512;
// Set the log report level
logConfig.level = RTM_LOG_LEVEL.INFO;
RtmConfig config = new RtmConfig();
config.appId = appId;
config.userId = userId;
config.logConfig = logConfig;
// Create the RTM Client
try
{
rtmClient = RtmClient.CreateAgoraRtmClient(config);
ShowMessage("RTM Client Initialize Sucessfull");
// Inactive unnecessary components
string[] activeComponentsList = { "Btn2" };
string[] inactiveComponentList = { "Btn3", "Btn4", "Btn5", "Btn6", "Btn7", "Btn8", "Btn9", "Btn10" };
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
catch (RTMException e)
{
ShowMessage(string.Format("{0} is failed", e.Status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", e.Status.ErrorCode, e.Status.Reason));
}
// Add listener
if (rtmClient != null)
{
// Add the message event listener
rtmClient.OnMessageEvent += OnMessageEvent;
// Add the presence event listener
rtmClient.OnPresenceEvent += OnPresenceEvent;
// Add the connection state change listener
rtmClient.OnConnectionStateChanged += OnConnectionStateChanged;
// Log in to the RTM server
var result = await rtmClient.LoginAsync(token);
var status = result.Status;
var response = result.Response;
if (status.Error)
{
ShowMessage(string.Format("{0} is failed", status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", status.ErrorCode, status.Reason));
Debug.Log(string.Format("Login failed"));
}
else
{
ShowMessage("Login Successfully");
}
//Subscribe to a Message Channel
SubscribeOptions options = new SubscribeOptions()
{
withMessage = true,
withPresence = true
};
var result2 = await rtmClient.SubscribeAsync(msChannelName, options);
var status2 = result2.Status;
var response2 = result2.Response;
if (status2.Error)
{
ShowMessage(string.Format("{0} is failed", status2.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", status2.ErrorCode, status2.Reason));
}
else
{
ShowMessage(string.Format("Subscribe channel success! at Channel:{0}", response2.ChannelName));
}
}
}
private void ChangeComponentView(string[] componentsList, string viewType)
{
for (int i = 0; i < componentsList.Length; i++)
{
if (viewType == "ACTIVATE")
GameObject.Find(componentsList[i]).GetComponent().interactable = true;
else if (viewType == "INACTIVATE")
GameObject.Find(componentsList[i]).GetComponent().interactable = false;
}
}
private void ShowMessage(string msg)
{
GameObject.Find("Txt3").GetComponent().text += "
" + msg;
}
// Implement the event listener handler
// Implement the connection state change event handler
private void OnConnectionStateChanged(string channelName, RTM_CONNECTION_STATE state, RTM_CONNECTION_CHANGE_REASON reason)
{
ShowMessage(string.Format("Channel:{0} connection state have changed to:{1} because of {2}", channelName, state, reason));
}
// Implement the presence event handler
private void OnPresenceEvent(PresenceEvent eve)
{
var channelName = eve.channelName;
var channelType = eve.channelType;
var eventType = eve.type;
var publisher = eve.publisher;
var stateItems = eve.stateItems;
var interval = eve.interval;
var snapshot = eve.snapshot;
Debug.Log(string.Format("The Event : {0} Happend", eventType));
switch (eventType)
{
case RTM_PRESENCE_EVENT_TYPE.SNAPSHOT:
ShowMessage(string.Format("You have sub or join channel:{0} channe type is:{1}", channelName, channelType));
break;
case RTM_PRESENCE_EVENT_TYPE.INTERVAL:
ShowMessage(string.Format("The channel:{0} channe type is:{1} is now in interval mode!", channelName, channelType));
break;
case RTM_PRESENCE_EVENT_TYPE.REMOTE_JOIN:
ShowMessage(string.Format("User:{0} sub or join channel:{1} channe type is:{2}", publisher, channelName, channelType));
break;
case RTM_PRESENCE_EVENT_TYPE.REMOTE_LEAVE:
ShowMessage(string.Format("User:{0} unsub or leave channel:{1} channe type is:{2}", publisher, channelName, channelType));
break;
case RTM_PRESENCE_EVENT_TYPE.REMOTE_TIMEOUT:
ShowMessage(string.Format("User:{0} timeout from channel:{1} channe type is:{2}", publisher, channelName, channelType));
break;
case RTM_PRESENCE_EVENT_TYPE.REMOTE_STATE_CHANGED:
ShowMessage(string.Format("User:{0} state change in channel:{1} channe type is:{2}", publisher, channelName, channelType));
break;
case RTM_PRESENCE_EVENT_TYPE.ERROR_OUT_OF_SERVICE:
ShowMessage(string.Format("User:{0} joined channel without presence service:{1} channe type is:{2}", publisher, channelName, channelType));
break;
}
}
// Implement the message event handler
private void OnMessageEvent(MessageEvent eve)
{
var channelName = eve.channelName;
var channelType = eve.channelType;
var topic = eve.channelTopic;
var publisher = eve.publisher;
var messageType = eve.messageType;
var customType = eve.customType;
var message = eve.message;
if (messageType == RTM_MESSAGE_TYPE.STRING)
{
var stMessage = message.GetData();
ShowMessage(string.Format("You have recieved a string type message: {0} from: {1} in channel:{2}", stMessage, publisher, channelName));
ShowMessage(string.Format("The channel type is {0}", channelType));
}
else
{
var biMessage = message.GetData();
ShowMessage(string.Format("You have recieved a binary type message: {0},from: {1} in channel:{2}", System.BitConverter.ToString(biMessage), publisher, channelName));
ShowMessage(string.Format("The channel type is {0}", channelType));
}
}
// OnClick Event Handler for Btn1
public async void PublishStringMessage()
{
// Publish string messages to a message channel
var message = GameObject.Find("InputField1").GetComponent().text;
if (rtmClient != null)
{
PublishOptions options = new PublishOptions()
{
channelType = RTM_CHANNEL_TYPE.MESSAGE,
customType = "PlainText"
};
var result = await rtmClient.PublishAsync(msChannelName, message, options);
var status = result.Status;
var response = result.Response;
if (status.Error)
{
ShowMessage(string.Format("{0} is failed, The error code is {1}", status.Operation, status.ErrorCode));
}
else
{
ShowMessage("Publish Message Success!");
}
}
}
// OnClick Event Handler for Btn6
public async void PublishBinaryMessage()
{
// Publish Binary Messahe to Stream Channel
var message = GameObject.Find("InputField2").GetComponent().text;
byte[] byteMsg = System.Text.Encoding.Default.GetBytes(message);
if (streamChannel != null)
{
TopicMessageOptions options = new TopicMessageOptions();
options.customType = "PlainBinary";
options.sendTs = 0;
var result = await streamChannel.PublishTopicMessageAsync(topicName, byteMsg, options);
var status = result.Status;
var response = result.Response;
if (status.Error)
{
ShowMessage(string.Format("{0} is failed", status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", status.ErrorCode, status.Reason));
}
else
{
ShowMessage("Publish Binary Message Success!");
}
}
}
// Onclick event handler for Btn2
public void CreateStChannel()
{
string[] activeComponentsList = { "Btn3", "Btn7" };
string[] inactiveComponentList = { "Btn2", "Btn4", "Btn5", "Btn6", "Btn8", "Btn9", "Btn10" };
// Create a Stream Channel
if (rtmClient != null)
{
streamChannel = rtmClient.CreateStreamChannel(stChannelName);
ShowMessage("Create Stream Channel:" + stChannelName + "Success !");
// Disable unnecessary components
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
}
// Onclick event handler for Btn3
public async void JoinStChannel()
{
string[] activeComponentsList = { "Btn4", "Btn5", "Btn8" };
string[] inactiveComponentList = { "Btn2", "Btn3", "Btn6", "Btn9", "Btn10", "Btn7" };
// Join a Stream Channel
if (streamChannel != null)
{
JoinChannelOptions options = new JoinChannelOptions();
options.withPresence = true;
options.token = rtcToken;
var result = await streamChannel.JoinAsync(options);
var status = result.Status;
var response = result.Response;
if (status.Error)
{
ShowMessage(string.Format("{0} is failed", status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", status.ErrorCode, status.Reason));
}
else
{
ShowMessage(string.Format("User:{0} Join stream channel success! at Channel:{1}", response.UserId, response.ChannelName));
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
}
}
// Onclick event handler for Btn4
public async void JoinTopic()
{
string[] activeComponentsList = { "Btn6", "Btn8", "Btn9" };
string[] inactiveComponentList = { "Btn2", "Btn3", "Btn4", "Btn7" };
// Join a topic
if (streamChannel != null)
{
JoinTopicOptions options = new JoinTopicOptions();
options.qos = RTM_MESSAGE_QOS.ORDERED;
options.syncWithMedia = false;
var result = await streamChannel.JoinTopicAsync(topicName, options);
var status = result.Status;
var response = result.Response;
if (status.Error)
{
ShowMessage(string.Format("{0} is failed", status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", status.ErrorCode, status.Reason));
}
else
{
ShowMessage(string.Format("User:{0} Join Topic:{1} success! at Channel:{2}", response.UserId, response.Topic, response.ChannelName));
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
}
}
// Onclick event handler for Btn5
public async void SubscribeTopic()
{
string[] activeComponentsList = { "Btn8", "Btn10" };
string[] inactiveComponentList = { "Btn2", "Btn3", "Btn5", "Btn7" };
// Subscribe a publisher from a topic
List userList = new List();
userList.Add("Tony");
TopicOptions options = new TopicOptions();
options.users = userList.ToArray();
var result = await streamChannel.SubscribeTopicAsync(topicName, options);
var status = result.Status;
var response = result.Response;
if (status.Error)
{
ShowMessage(string.Format("{0} is failed", status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", status.ErrorCode, status.Reason));
}
else
{
ShowMessage(string.Format("User:{0} Subscribe Topic:{1} success! at Channel:{2}", response.UserId, response.Topic, response.ChannelName));
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
}
// Onclick event handler for Btn7
public async void ReleaseStChannel()
{
string[] activeComponentsList = { "Btn2" };
string[] inactiveComponentList = { "Btn3", "Btn4", "Btn5", "Btn6", "Btn7", "Btn8", "Btn9", "Btn10" };
// Release a Stream Channel
if (rtmClient != null && streamChannel != null)
{
var result = await streamChannel.LeaveAsync();
var status = result.Status;
var response = result.Response;
if (status.Error)
{
ShowMessage(string.Format("{0} is failed", status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", status.ErrorCode, status.Reason));
}
else
{
ShowMessage("Dispose Channel Success!");
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
var status1 = streamChannel.Dispose();
}
}
// Onclick event handler for Btn8
public async void LeaveStChannel()
{
string[] activeComponentsList = { "Btn3", "Btn7" };
string[] inactiveComponentList = { "Btn2", "Btn4", "Btn5", "Btn6", "Btn8", "Btn9", "Btn10" };
// Leave a Stream Channel
if (streamChannel != null)
{
var result = await streamChannel.LeaveAsync();
var status = result.Status;
var response = result.Response;
if (status.Error)
{
ShowMessage(string.Format("{0} is failed", status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", status.ErrorCode, status.Reason));
}
else
{
ShowMessage(string.Format("User:{0} Leave stream channel success! at Channel:{1}", response.UserId, response.ChannelName));
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
}
}
// Onclick event handler for Btn9
public async void LeaveTopic()
{
string[] activeComponentsList = { "Btn4", "Btn8" };
string[] inactiveComponentList = { "Btn2", "Btn3", "Btn6", "Btn7", "Btn9" };
// Leave a topic
if (streamChannel != null)
{
var result = await streamChannel.LeaveTopicAsync(topicName);
var status = result.Status;
var response = result.Response;
if (status.Error)
{
ShowMessage(string.Format("{0} is failed", status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", status.ErrorCode, status.Reason));
}
else
{
ShowMessage(string.Format("User:{0} Leave Topic:{1} success! at Channel:{2}", response.UserId, response.Topic, response.ChannelName));
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
}
}
// Onclick event handler for Btn10
public async void UnsubscribeTopic()
{
string[] activeComponentsList = { "Btn5", "Btn8" };
string[] inactiveComponentList = { "Btn2", "Btn3", "Btn7", "Btn10" };
// Unsubscribe a publisher from a topic
List userList = new List();
userList.Add("Tony");
TopicOptions options = new TopicOptions();
options.users = userList.ToArray();
var result = await streamChannel.UnsubscribeTopicAsync(topicName, options);
var status = result.Status;
var response = result.Response;
if (status.Error)
{
ShowMessage(string.Format("{0} is failed", status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", status.ErrorCode, status.Reason));
}
else
{
ShowMessage(string.Format("Unsubscribe Topic Success!"));
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
}
private async void OnDestroy()
{
string[] activeComponentsList = { "Btn4", "Btn5", "Btn8" };
string[] inactiveComponentList = { "Btn2", "Btn3", "Btn6", "Btn9", "Btn10", "Btn7" };
// Dispose a Stream Channel
if (rtmClient != null && streamChannel != null)
{
var result = await streamChannel.LeaveAsync();
var status = result.Status;
var response = result.Response;
if (status.Error)
{
ShowMessage(string.Format("{0} is failed", status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", status.ErrorCode, status.Reason));
}
else
{
ShowMessage("Dispose Channel Success!");
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
var status1 = streamChannel.Dispose();
}
if (rtmClient != null)
{
var result = await rtmClient.UnsubscribeAsync(msChannelName);
var status2 = result.Status;
var response2 = result.Response;
if (status2.Error)
{
Debug.Log(string.Format("{0} is failed", status2.Operation));
Debug.Log(string.Format("The error code is {0}, because of: {1}", status2.ErrorCode, status2.Reason));
}
else
{
Debug.Log("Unsubscribe Channel Success!");
}
var status3 = rtmClient.Dispose();
Debug.Log("Dispose rtmClient Success!");
rtmClient = null;
}
}
}
```
Follow the implementation steps to understand the core API calls in the sample code or use the snippets in your own code.
### Import Agora classes [#import-agora-classes-1]
To use the relevant Unity and Signaling APIs in your project, import the corresponding namespaces:
```csharp
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using Agora.Rtm;
```
### Initialize the Signaling engine [#initialize-the-signaling-engine-5]
Before calling any other Signaling SDK API, initialize an `IRtmClient` instance.
```csharp
if (string.IsNullOrEmpty(userId) || string.IsNullOrEmpty(appId))
{
ShowMessage("We need a userId and appId to initialize!");
return;
}
RtmLogConfig logConfig = new RtmLogConfig();
// Set log file path
logConfig.filePath = "myFilePath";
// Set the file size of the agore.log file
logConfig.fileSizeInKB = 512;
// Set the log report level
logConfig.level = RTM_LOG_LEVEL.INFO;
RtmConfig config = new RtmConfig();
config.appId = appId;
config.userId = userId;
config.logConfig = logConfig;
// Create the RTM Client
try
{
rtmClient = RtmClient.CreateAgoraRtmClient(config);
ShowMessage("RTM Client Initialize Sucessfull");
// Inactive unnecessary components
string[] activeComponentsList = { "Btn2" };
string[] inactiveComponentList = { "Btn3", "Btn4", "Btn5", "Btn6", "Btn7", "Btn8", "Btn9", "Btn10" };
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
catch (RTMException e)
{
ShowMessage(string.Format("{0} is failed", e.Status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", e.Status.ErrorCode, e.Status.Reason));
}
```
### Add an event listener [#add-an-event-listener-5]
The event listener enables you to implement the processing logic in response to Signaling events. Use the following code to handle event notifications or display received messages:
```csharp
private void OnConnectionStateChanged(string channelName, RTM_CONNECTION_STATE state, RTM_CONNECTION_CHANGE_REASON reason)
{
ShowMessage(string.Format("Channel:{0} connection state have changed to:{1} because of {2}", channelName, state, reason));
}
// Implement the presence event handler
private void OnPresenceEvent(PresenceEvent eve)
{
var channelName = eve.channelName;
var channelType = eve.channelType;
var eventType = eve.type;
var publisher = eve.publisher;
var stateItems = eve.stateItems;
var interval = eve.interval;
var snapshot = eve.snapshot;
Debug.Log(string.Format("The Event : {0} Happend", eventType));
switch (eventType)
{
case RTM_PRESENCE_EVENT_TYPE.SNAPSHOT:
ShowMessage(string.Format("You have sub or join channel:{0} channe type is:{1}", channelName, channelType));
break;
case RTM_PRESENCE_EVENT_TYPE.INTERVAL:
ShowMessage(string.Format("The channel:{0} channe type is:{1} is now in interval mode!", channelName, channelType));
break;
case RTM_PRESENCE_EVENT_TYPE.REMOTE_JOIN:
ShowMessage(string.Format("User:{0} sub or join channel:{1} channe type is:{2}", publisher, channelName, channelType));
break;
case RTM_PRESENCE_EVENT_TYPE.REMOTE_LEAVE:
ShowMessage(string.Format("User:{0} unsub or leave channel:{1} channe type is:{2}", publisher, channelName, channelType));
break;
case RTM_PRESENCE_EVENT_TYPE.REMOTE_TIMEOUT:
ShowMessage(string.Format("User:{0} timeout from channel:{1} channe type is:{2}", publisher, channelName, channelType));
break;
case RTM_PRESENCE_EVENT_TYPE.REMOTE_STATE_CHANGED:
ShowMessage(string.Format("User:{0} state change in channel:{1} channe type is:{2}", publisher, channelName, channelType));
break;
case RTM_PRESENCE_EVENT_TYPE.ERROR_OUT_OF_SERVICE:
ShowMessage(string.Format("User:{0} joined channel without presence service:{1} channe type is:{2}", publisher, channelName, channelType));
break;
}
}
// Implement the message event handler
private void OnMessageEvent(MessageEvent eve)
{
var channelName = eve.channelName;
var channelType = eve.channelType;
var topic = eve.channelTopic;
var publisher = eve.publisher;
var messageType = eve.messageType;
var customType = eve.customType;
var message = eve.message;
if (messageType == RTM_MESSAGE_TYPE.STRING)
{
var stMessage = message.GetData();
ShowMessage(string.Format("You have recieved a string type message: {0} from: {1} in channel:{2}", stMessage, publisher, channelName));
ShowMessage(string.Format("The channel type is {0}", channelType));
}
else
{
var biMessage = message.GetData();
ShowMessage(string.Format("You have recieved a binary type message: {0},from: {1} in channel:{2}", System.BitConverter.ToString(biMessage), publisher, channelName));
ShowMessage(string.Format("The channel type is {0}", channelType));
}
}
```
### Log in to Signaling [#log-in-to-signaling-5]
To connect to Signaling and access Signaling network resources, such as sending messages, and subscribing to channels, call `login`.
During a login operation, the client attempts to establish a connection with Signaling. Once the connection is established, the client transmits heartbeat information to the Signaling server at fixed intervals to keep the client active until the client actively logs out or is disconnected. The connection is interrupted when timeout occurs. During this period, users may freely access the Signaling network resources subject to their own permissions and usage restrictions.
```csharp
// Log in to Signaling
var result = await rtmClient.LoginAsync(token);
var status = result.Status;
var response = result.Response;
if (status.Error)
{
ShowMessage(string.Format("{0} is failed", status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", status.ErrorCode, status.Reason));
}
else
{
ShowMessage("Login Successfully");
}
```
Use the `LoginAsync` return value, or listen to the `OnConnectionStateChanged` event notification to confirm that login is successful or obtain the error code and cause of login failure. When performing a login operation, the client's network connection state is `CONNECTING`. After a successful login, the state is updated to `CONNECTED`.
Best practice
To continuously monitor the network connection state of the client, best practice is to continue to listen for `OnConnectionStateChanged` event notifications throughout the life cycle of the application. For further details, see [Event listeners](/en/realtime-media/rtm/build/send-and-receive-messages/add-event-listener).
After a user successfully logs into Signaling, the application's PCU increases, which affects your billing data.
### Send a message [#send-a-message-4]
To distribute a message to all subscribers of a message channel, call `PublishAsync`. The following code sends a string type message.
```csharp
// Send a message to a channel
var message = GameObject.Find("InputField1").GetComponent().text;
if (rtmClient != null)
{
PublishOptions options = new PublishOptions()
{
channelType = RTM_CHANNEL_TYPE.MESSAGE,
customType = "PlainText"
};
var result = await rtmClient.PublishAsync(msChannelName, message, options);
var status = result.Status;
var response = result.Response;
if (status.Error)
{
ShowMessage(string.Format("{0} is failed, The error code is {1}", status.Operation, status.ErrorCode));
}
else
{
ShowMessage("Publish Message Success!");
}
}
```
Before calling `PublishAsync` to send a message, serialize the message payload as a string.
### Subscribe and unsubscribe [#subscribe-and-unsubscribe-5]
To receive messages sent to a channel, call `SubscribeAsync` to subscribe to channel messages:
```csharp
//Subscribe to a Message Channel
SubscribeOptions options = new SubscribeOptions()
{
withMessage = true,
withPresence = true
};
var result2 = await rtmClient.SubscribeAsync(msChannelName, options);
var status2 = result2.Status;
var response2 = result2.Response;
if (status2.Error)
{
ShowMessage(string.Format("{0} is failed", status2.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", status2.ErrorCode, status2.Reason));
}
else
{
ShowMessage(string.Format("Subscribe channel success! at Channel:{0}", response2.ChannelName));
}
```
When you no longer need to receive messages from a channel, call `UnsubscribeAsync` to unsubscribe from the channel:
```csharp
if (rtmClient != null)
{
var result = await rtmClient.UnsubscribeAsync(msChannelName);
var status2 = result.Status;
var response2 = result.Response;
if (status2.Error)
{
Debug.Log(string.Format("{0} is failed", status2.Operation));
Debug.Log(string.Format("The error code is {0}, because of: {1}", status2.ErrorCode, status2.Reason));
}
else
{
Debug.Log("Unsubscribe Channel Success!");
}
var status3 = rtmClient.Dispose();
Debug.Log("Dispose rtmClient Success!");
rtmClient = null;
}
```
For more information about sending and receiving messages see [Message channels](/en/realtime-media/rtm/build/work-with-channels/message-channel) and [Stream channels](/en/realtime-media/rtm/build/work-with-channels/stream-channel).
### Log out of Signaling [#log-out-of-signaling-5]
When a user no longer needs to use Signaling, call `LogoutAsync`. Logging out means closing the connection between the client and Signaling. The user is automatically logged out or unsubscribed from all message and stream channels. Other users in the channel receive an `OnPresenceEvent` notification of the user leaving the channel.
```csharp
// Logout of Signaling
var (status,response) = await rtmClient.LogoutAsync();
```
## Test Signaling [#test-signaling-5]
Take the following steps to test the sample code:
1. In your code, replace `your_appId` with your app ID from Agora Console and `your_userId` with a numeric string.
2. Use the [Token Builder](https://agora-token-generator-demo.vercel.app/) to generate a Signaling token:
1. Select Signaling from the Agora products dropdown.
2. Fill in your app ID and app certificate from [Agora Console](https://console.agora.io/v2). Leave the remaining fields blank.
3. Click **Generate Token**.
4. In your code, replace `your_token` with the generated token.
3. Repeat the previous step to generate a stream channel token but this time also fill in the channel name with the value for `stChannelName` and the User ID with the value for `userId` from your code. Use the generated token to replace `your_RTC_token` in your code.
4. In the Unity Editor, mount the event handling functions defined in the code to the corresponding Button components.
5. In Unity, click **Play**. You see the game running on your device.
6. Run another instance of the game with the same `appId` but a different `userId` and tokens.
7. In either game instance, type a message in the input box under **Send string message to message channel** and press **Send Message**.
You see the message displayed in the other game instance.
Congratulations! You have successfully integrated Signaling into your project.
## Reference [#reference-5]
This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.
### Token authentication [#token-authentication-5]
In this guide you retrieve a temporary token from a [Token Builder](https://agora-token-generator-demo.vercel.app/). To understand how to create an authentication server for development purposes, see [Secure authentication with tokens](./build/connect-and-authenticate/authentication-workflow).
### API reference [#api-reference-5]
* [API reference](/en/api-reference/api-ref/signaling)
* [Event listeners](./build/send-and-receive-messages/add-event-listener)
<_PlatformProcessedMarker close="true" />
# Security (/en/realtime-media/rtm/security)
## Information security policy [#information-security-policy]
This section provides information security policy for Agora services.
Agora services provide built-in encryption and customized encryption. You can use either of them to implement encryption. The following diagram describes the encrypted data transmission process:

#### Purpose [#purpose]
Agora is committed to safeguarding the confidentiality, integrity, and availability of all users' physical and electronic information assets.
* Confidentiality against unauthorized access and eavesdropping
* Integrity against tampering and forgery
* Availability of data transmission through the Agora SDRTN®
#### Scope [#scope]
This article describes how Agora protects customer data with security controls.
### Data classification [#data-classification]
All customer data, in all formats or media types, is classified according to the following categories and protected accordingly.
| Category | Description |
| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Customer Account Data | Information related to the customers' Agora accounts, including customer ID, customer IP, network type, operating system, email address, telephone, product interest, programing platform, UTM information, identity, company name, company URL, billing information, trade information, purchased package information, project status, ticket, member information and console access record. |
| End User Data | Information collected by customers related to end users' personal devices and network, including microphone and camera information, CPU status, memory status, battery status, system version, phone model, phone signal level, received signal strength indicator (RSSI), network type, user attributes and channel attributes. |
| Call Content | Audio and video data of the user during a call. |
| Log | Media server logs generated by the Agora servers when accessing the Agora SDRTN®. Media server logs do not contain text messages or personal information. |
### Data security [#data-security]
The communication between the user and the Agora server is protected by transmission protocols, such as the Agora private transmission protocol, Transport Layer Security (TLS) and Web Socket Secure (WSS). You can also use Advanced Encryption Standard (AES) or a customized encryption algorithm for the encryption of audio and video data.
During data transmission, the Agora SDRTN® does not transmit any encryption key information. Call content information can only be decrypted on the terminal device (such as the client app and the customer's on-premise recording server) through the client authorization key.
### Data availability [#data-availability]
* Large and distributed data centers: Agora has multiple data centers providing services globally, and any attack on one data center cannot affect others.
* Rapid recovery: When a data center is subjected to malicious attacks that are difficult to prevent, such as a distributed denial-of-service (DDoS) attack, Agora will automatically isolate the data center and avoid affecting users' services.
* DDoS attack prevention: Agora has deployed anti-DDoS firewalls in each core cloud data center. Agora has more than two hundred distributed data centers around the world, which guarantees sufficient capabilities and resources to control the risk of DDoS attacks.
### Data storage [#data-storage]
Agora provides customers with the Agora On-Premise Recording SDK and Agora Cloud Recording, enabling customers to record part or all of the call contents. When using the recording services, all recorded video or audio files are stored on the storage server provided by the customer.
### Access authorization [#access-authorization]
End users can access the Agora SDRTN® using a dynamic key, see [Secure authentication with tokens](/en/realtime-media/video/build/authenticate-users/authentication-workflow).
### Electronic access controls [#electronic-access-controls]
Agora strictly controls the data access on all internal systems. All users have independent internal accounts and authorization systems, and must pass two-step verification. All access records are recorded.
All servers involving user data are strictly audited and protected. Agora employees will only access the production server when necessary, and only by obtaining temporary authorization. Operation records are kept for the whole process.
### Physical access controls [#physical-access-controls]
All operating servers are hosted in computer rooms that meet ISO 27001 or above information security management certification standards, and all computer rooms protect information security in accordance with the escrow agreement or the Data Protection Association (DPA). Both third-party hosting providers and Agora employees must be approved before they can access servers and equipment.
### Roles and responsibilities [#roles-and-responsibilities]
Agora sets the responsibilities for its own staff and customers. See the following sections for more information.
### Roles and responsibilities within Agora [#roles-and-responsibilities-within-agora]
Security roles and responsibilities within Agora are categorized as follows:
| Role | Responsibilities |
| ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Information Security Sub-Committee | The Information Security Sub-committee (ISSC) is responsible for the development and implementation of policies and procedures. The ISSC monitors company adherence, and conducts regular technical and non-technical evaluations of Agora security policies. The ISSC also designates which employees are authorized employees. |
| Authorized Employee | An authorized employee has access to the production machines required for the support and maintenance of the Agora service as determined by the ISSC. |
| All Other Staffs | All other staff are required to maintain confidentiality as required by their terms of employment and are required to immediately report of any security breach. |
### Shared responsibilities [#shared-responsibilities]
When evaluating the Agora SDRTN®, it is important for customers to understand and distinguish the following security measures:
* The security measures that Agora implements and operates.
* The security measures that customers implement and operate to ensure the security of call content information and application when using the Agora SDRTN® services.
Customers retain control of the security measures they choose to protect their personal information, platform, applications, systems, and networks. Agora customers should be responsible for all information they collect from users and themselves, such as application logins, identities, passwords, payment information, names, and addresses.
Agora recommends adding obvious prompts before accessing the users' personal information. For example, add an Enable/Disable button with which the user can agree or disagree to share personal information.
### Security training [#security-training]
Agora conducts information protection, security and compliance training for all new employees. Agora also ensures that all employees receive information confidentiality training at least once a year. All authorized employees (employees who have access to the production machines) will receive additional training.
### Non-compliance [#non-compliance]
Employees must comply with confidentiality agreements and internal security systems. If a non-compliant situation occurs, Agora will take the corresponding measures depending on the severity of the situation, including but not limited to having conversations with the at-fault employees, strengthening training and education, dissolving labor agreements, and pursuing other legal liabilities.
### Reporting potential security risks [#reporting-potential-security-risks]
Please report any potential risks of Agora services that you may notice to [security@agora.io](mailto\:security@agora.io). Please use our PGP public key (Key ID: 2F4553BE) to encrypt your report, as potential security risks are usually sensitive. We assure our security experts will handle the issue immediately once they receive your report.
To facilitate troubleshooting and verification, please include the following information in your email:
* Your contact information.
* The version of the impacted SDK or solution.
* Description of the potential risks.
* Miscellaneous technical details, such as your system configurations, methods or steps for reproducing the issue.
To better protect our system and customers, Agora also invites security researchers to report any bug or vulnerabilities they discover to us. Click [Bug Bounty](#agora-bug-bounty-program) to know more.
### Security FAQ [#security-faq]
To ensure the security of the transmitted data, Agora services provide the encryption for audio and video data. Customers can use AES-128/AES-256 or other algorithms preset by Agora, or use customized encryption algorithms to achieve encryption, see [Channel Encryption](/en/realtime-media/video/build/secure-and-protect-channels/media-stream-encryption). The encryption key is completely generated and distributed by the customer. Agora recommends that customers use separate keys for each channel to achieve the highest level of data security.
For network protocol encryption, Agora SDKs use AES\_128 mode to encrypt network payload.
For channel encryption, Agora SDKs support AES\_128/AES\_256 mode to encrypt audio and video. For those who have higher requirements for content security, Agora recommends using our customized encryption. Note that the On-Premise Recording SDK and Cloud Recording does not support customized encryption. For details, please send email to [support@agora.io](mailto\:support@agora.io).
Yes.
Agora regularly scans core network nodes to check and clear possible security holes. Agora also configures anti-DDoS firewalls in each core cloud data center to protect them from attacks. Agora has more than two hundred distributed data centers around the world, which guarantees sufficient capabilities and resources to control the risk of DDoS attacks.
When using Agora services, the audio and video data are transmitted through the Agora servers. Agora servers cache the audio and video data for 10 seconds during the transmission and release all audio and video data immediately after the call.
## Whitepaper [#whitepaper]
People engage longer when they see, hear, and interact with each other. The future of meaningful human connections is made possible now with Agora’s Real-Time Engagement Platform. People rely on Agora’s Real-Time Engagement Platform to exchange millions of calls and messages, with vivid voice and video embedded in any application, on any device, anywhere.
Agora, Inc. is proud to offer the professional Real-Time Engagement Platform as a Service (RTE PaaS) with Compliance, Safety, Security, and Trust. Our commitment to compliance, data, and information security and privacy protection is a part of the core values of our company. From our Software-Defined Real-Time Network (SDRTN®) architecture to our day-to-day business operations, Agora continually invests in innovations and business processes that build trust with our customers, investors, and developer community. Agora works to high standards to follow the best security practices and comply with strict privacy regulations and standards as we respect the privacy of all our customers.
The information contained in this document is intended to provide transparency in relation to Agora’s security stance and processes. If you think you may have found a security vulnerability within any of Agora’s services, please contact our security team directly at [security@agora.io](mailto\:security@agora.io).
Agora thrives to incorporate security into all our products and services and integrates the best security practices into everyday business operations. To meet these primary goals and improve the overall information security posture in an efficient and effective manner, Agora has built its security framework against the ISO27001 Information Security Management Standard.
As threats to information security continue to evolve, having dedicated security resources is essential. Agora’s Executive Security Committee meets regularly to address security concerns and coordinate company-wide security initiatives. The Agora Strategic Security Program Roadmap has been developed and approved by the committee to guide the implementation of our security programs. Our dedicated security team, led by the Chief Information Security Officer, has the responsibility for building and enforcing information security programs.
Agora is always looking to better protect its systems and customers. Therefore, we continuously monitor and improve our Information Security Programs by implementing the following:
* Agora conducts an internal audit of its information security management system at least once a year to ensure effectiveness;
* Agora follows industry best practice software development lifecycle management processes and conducts internal security reviews and testing before deployment into production systems;
* Agora engages third-party security experts to carry out regular penetration tests;
* Through our Bug Bounty Program, Agora works with security researchers to keep our customers data more secure by identifying and reporting vulnerabilities in our products and services.
Agora adheres to regional and international information security standards as well as industry requirements and is committed to using international best practices. We engage with independent third parties to verify the compliance of Agora. Certified by various reputable agencies across the globe, we are recognized by industry and security organizations for excellence.
**ISO/IEC 27001 Information Security Management Standard**
Agora is certified to ISO/IEC 27001:2022 by DNV GL, demonstrating our information security maturity level. Our security team implements the Information Security Management System in partnership with Ernst & Young. Security is a top priority at Agora, and this achievement demonstrates our commitment and continuous efforts to improve the efficiency of information security controls.
Download the certificate: [ISO/IEC 27001:2022](/files/Agora_ISO_27001.pdf)
**ISO/IEC 27018 Information Technology – Security techniques – Code of practice for protection of personally identifiable information (PII) in publics clouds acting as PII processors**
Agora is certified to ISO/IEC 27018 by DNV GL. This standard is a Code of Practice for protecting personal data in the cloud environment. Agora continuously strives to protect the sensitive data with our customers.
Download the certificate: [ISO/IEC 27018:2019](/files/Agora_ISO_27018.pdf)
**SOC 2 Report**
Agora is confident with our security practices and we continue to engage independent third parties to perform a strict SOC 2 audit on our internal processes, security controls and the design of Agora products. We meet all audit requirements set by the American Institute of Certified Public Accounts (AICPA) standards for security, availability and confidently and achieves the SOC 2 report.
**PCI DSS**
Agora prioritizes the security and privacy of its customers. Since we are neither a merchant nor a service provider as defined by the PCI Security Standards Council, we do not need to conduct an annual PCI assessment. However, Agora ensures that all transactions and customer data are handled with the highest level of security, in alignment with industry best practices. Should you have any questions or require further information regarding our security measures, please feel free to contact us at [security@agora.io](mailto\:security@agora.io).
**General Data Protection Regulation - GDPR**
Agora is aligned with GDPR and we are committed to providing GDPR compliant products and services to our customers in the EU region or with our customers who conduct business within the EU.
**Health Insurance Portability and Accountability Act -HIPAA**
Agora is aware of the sensitivity of transmitting and processing health information and we have invested in both the creation and ongoing maintenance of a HIPAA compliance program.
**California Consumer Privacy Act - CCPA**
The CCPA is the first comprehensive privacy law in the United States that aims to provide a variety of privacy rights to California consumers. As a service provider, Agora is aligned with CCPA though the implementation of our security programs.
**Children’s Online Privacy Protection Act - COPPA**
The COPPA regulates the privacy protection requirements for children under the age of thirteen. Agora has engaged privacy experts in meeting the requirements of COPPA.
Agora strongly believes in the principles of Secure by Design and Defense in Depth. Therefore, Agora adopts industry recognized security standards and best security practice at every layer - from infrastructure to application, in order to perfect our products and environment, and to secure the organization and our customers.
Securing access to your environment starts with identity and access controls. Agora provides you with a solution to ensure that only authorized people can access your services and resources. The Agora Console is a role-based access control tool that you utilize to restrict access based on "the-need-to-know" principle. The console is an interactive interface where you can easily create accounts, revoke members and assign roles and permissions. This tool can assist you to enforce your security policies.
Furthermore, Agora provides static key, dynamic key, and hybrid authentication methods to secure the communication channels in different use-cases.
At Agora, you choose how your content is secured. We offer you various options for your content in transit and provide you with full control of your own encryption keys. There features include:
* Agora Software Development Kits (SDKs) provide built-in encryption algorithm including AES-128 and AES-256 to protect all data transmitted between the end users and Agora services.
* We also provide you with the choice to use your own encryption algorithm to protect users’ media stream during real-time engagement. The encryption key is completely under your control.
* The communication between the end users and Agora network (that is, Agora SDRTN®) is protected by encrypted transmission protocols such as the Agora Private Transmission Protocol, Transport Layer Security (TLS) and WebSocket Secure (WSS).
Agora offers our customers the ability to record the real-time communication with Agora On-Premise Recording SDK and Agora Cloud Recording SDK. The recording files can be stored on the users’ local device or can be stored in a designated cloud storage service chosen by our customers. The local recordings or cloud recordings can be further encrypted through any encryption form of your choosing.
Agora does not store any streaming data or user data except for caching for transmission purpose. The cached streaming data of users will be immediately released after the service.
Data centers hosting Agora services are maintained by certified and industry-leading cloud service providers, offering state-of-the-art physical protection for the servers and infrastructure that comprise the Agora environment.
The production environment, where all our customers data and functional servers reside, are completely separated from our internal organization network including the development and testing environments. This guarantees that all our customers data will stay in the production environment and never be used for development or testing purposes.
Access to the Agora production and non-production network is minimized to the greatest extent. Agora also implements network segmentation in production network based on various factors such as the type of business, the criticality of data, and the potential risks, to secure the sensitive customer data.
**DDoS prevention**
Agora regularly scans our core network nodes in the production environment to check and clear potential security vulnerabilities Anti-DDoS firewalls are configured in each core cloud data center for protection. With more than two hundred distributed data centers around the world, Agora can guarantee you with sufficient capabilities and resources to minimize the impact of DDoS attacks and ensure high-availability of real-time video and audio anywhere around the globe.
**Monitoring, logging and analysis**
Agora continuously monitors and analyzes log events to gain a comprehensive view of the security state of our production environment. The logging covers both successful and unsuccessful security events, with an emphasis on the event data of critical infrastructure.
To provide customers with better visibility and security insights, Agora Analytics is made available to consumers as a tracking and analysis tool. This tool enables customers to efficiently locate quality issues and identify root causes for a better end user experience.
The tool contains the Real-Time Alarm function, which enables you to monitor call quality and informs you in real time when the user’s communication experience is below expectation. With Real-Time Alarm, you can undertake the following actions in real time:
* Monitor users who are having poor communication experience.
* Identify abnormalities, analyze quality factor, and locate the source of an abnormal issue.
**Network geo-fencing**
Agora has embedded Network Geo-Fencing in Real-Time Voice, Video and Messaging SDKs to ensure your data is secured from the rising concerns in network security and privacy breaches. Agora Network Geo-Fencing establishes a virtual boundary within Agora SDRTN® and you have the choice to restricted your network traffic in one or more designated region(s).
**Network redundancy**
Agora has more than 200 data centers POPs (Points of Presence) across the world, covering the United States, Europe, China, Japan, India and Asia Pacific and other areas. The POPs in the SD-TRN network adopt the full mesh topology with superior routing capabilities. This is to ensure that the network services are not interrupted due to a single point of failure. The POPs build fault tolerance and disaster recovery capability of Agora service across the regions.
POPs also measure the performance of every possible path through the global network to find the “optimized” paths to ensure high data packet delivery success rate within the smallest time window.
Agora provides expert guidance to our customers on how to leverage our security features and embed best practices into every layer of your application.
Agora is continuously monitoring, auditing, and improving the design and operating effectiveness of our security controls. These activities are regularly performed by both third-party credentialed assessors and Agora’s internal risk and compliance team. Audit results are shared with senior management and all findings are tracked to resolution in a timely manner.
In addition to third-party security compliance audits, Agora engages Trustwave SpiderLabs to conduct network penetration tests at least annually. Results of the penetration testing are shared with senior management and are triaged, prioritized and remediated in time. Agora customers may receive executive summaries of these activities by requesting them from their account managers.
Data security and user privacy are the top priorities of Agora. Agora is committed to building a professional RTE PaaS with Compliance, Safety, Security, and Trust. It is a critical responsibility for Agora to help ensure the confidentiality, integrity, and availability of systems and data, and Agora continues to work hard to maintain that trust. If you have any questions or concerns, please contact our security team or account managers.
## ISO certificates [#iso-certificates]
Security and compliance are the basic requirement for real-time interaction. Agora complies with the compliance requirements of different countries and industries to create safe and reliable cloud services.
The ISO certification of Agora has passed the supervision and accreditation of three different certification organizations: IFA CNAS (China National Accreditation Service for Conformity Assessment), UKAS (United Kingdom Accreditation Service), and DNV (Det Norske Veritas). Agora builds cloud service technology worldwide in accordance with internationally recognized standards to ensure the information security of global customers.
#### ISO/IEC 27001:2022 [#isoiec-27001]
ISO/IEC 27001:2022 is the most authoritative, widely accepted and applied system certification standard in the field of information security. This system covers the information security management during the global operation and maintenance of Agora's cloud platform for real-time engagement.
Download the certificate: [ISO/IEC 27001:2022](/files/Agora_ISO_27001.pdf)
#### ISO/IEC 27017:2015 [#isoiec-27017]
ISO/IEC 27017:2015 is a supplementary standard to ISO 27001, which provides information security implementation specifications for cloud service providers.
Download the certificate: [ISO/IEC 27017:2015](/files/Agora_ISO_27017.pdf)
#### ISO/IEC 27018:2019 [#isoiec-27018]
ISO/IEC 27018:2019 is a supplementary standard to ISO 27001, which provides implementation specifications for the protection of personal information for cloud service providers.
Download the certificate: [ISO/IEC 27018:2019](/files/Agora_ISO_27018.pdf)
#### ISO/IEC 27701:2019 [#isoiec-27701]
ISO/IEC 27701:2019 is a privacy extension to ISO/IEC 27001 information security management and ISO/IEC 27002 security controls. It is an international standard system of management systems that provides guidance on the protection of personal privacy, including how organizations should manage personal information.
Download the certificate: [ISO/IEC 27701:2019](/files/Agora_ISO_27701.pdf)
## Security best practices [#security-best-practices]
Security and compliance are essential for real-time engagements through technology. In order to provide safe and reliable cloud services, Agora adheres to the compliance requirements of different countries, regions, and industries, in addition to being certified to ISO/IEC 27001. For more information, see [ISO certificates](#iso-certificates).
Agora products and services are designed and built with multiple protection measures against attacks commonly seen in the real-time engagement industry. This article describes some of the security best practices that Agora has adopted, as well as security tools it provides for developers, as follows:
| Protection measures | Applied by default | Recommended use-cases |
| -------------------------- | ------------------ | ---------------------------------------------------------------------------------------------------------- |
| Channel seperation | Yes | All real-time use-cases. |
| Token-based authentication | No | All real-time apps in a production environment should use token-based end-user authentication. |
| Encryption | No | Real-time use-cases that require confidentiality. |
| Network geofencing | No | Real-time use-cases where customers wish to restrict access to Agora servers to within a specified region. |
### Level 1 - Channels separation [#level-1---channels-separation]
The channels architect is the first built-in layer of protection. Agora creates an independent and isolated channel for each audio, video, or messaging data transmission. All channels are logically separated and only authenticated users from the same App ID can join the same channel.
### Level 2 - Token-based authentication [#level-2---token-based-authentication]
The second layer of protection is on the authentication layer. This is done via using the dynamic token-based authentication. The token is a short-lived access key, which is generated by the app backend and allows users to access the Agora platform after the user is properly validated by the app.
A token is generated with important information such as App ID, user ID (`uid`), channel name, and expiration date.

The app developer can enable token-based authentication (App Certificate) on [Agora Console](https://console.agora.io/v2). When enabled, all user’s request to join a channel must be done with a valid token.

Please note for best security practices, you need to set the token expiration time (24 hours by default). A token has three expiration timestamps:
* Token expiration: How long a token is valid for, that is, how long a user can stay in a channel.
* Privilege expiration:
* Join a channel: How long a token can be used to join a channel.
* Streaming privilege: Whether a user can send audio, video or messaging to the channel, or join as an audience member. This privilege is not enabled by default.
### Level 3 - Encryption [#level-3---encryption]
The next layer of security in the Agora platform is encrytion. Agora supports transmission encryption and data encryption.
To guarantee data confidentiality during transmission, Agora uses the AUT (Agora Universal Transport) encryption protocol, Agora's proprietary secured transport layer.
Data encryption encrypts all the audio and video streams with a symmetric key and encryption controlled by the app developer. In this level the app provides a symmetric encryption key to the local SDK libraries.
The SDK encrypts all the captured media using the key and the configured encryptions using AES 128/256 (based on configuration). The data is sent encrypted to Agora SDRTN® and from there to the other endpoints in the channel. The receiving endpoint will use the key provided by the app layer to decrypt the media streams and send to the renders. With this method, only the application knows the keys. In the Native SDK (iOS, Android, Mac, Windows) the keys are not sent to Agora servers.

When using Other Agora services like Web SDK, Cloud Recording, Content Moderation, Transcoding etc, encryption is done a bit differently and it is not end-to-end. In this case media is still encrypted but the Agora service needs to be aware of the key to be able to connect to the channel and provide service. For example, in Web SDK the User/Browser protection is done via the web-server protection (HTTPS) as well as the WebRTC standard, security practice (encryption, key management etc). More information on WebRTC security can be found [here](https://webrtc-security.github.io/).
The media encryption on Web SDK is done via WebRTC standard but the interoperability to Agora is done using Agora’s encryption engine and the encryption key is passed securely to the Web SDK servers via the APIs. The key is required as the Agora edge server will convert from WebRTC protocols to Agora’s protocol and allow interoperability with Native SDK. Similar situation happens with Cloud Recording, Content Moderation etc., where the Restful APIs are used to securely pass the key for the channel.

### Level 4 - Network geofencing [#level-4---network-geofencing]
To conform to the laws and regulations of different countries and regions, the Agora Video SDK and the Signaling SDK support network geofencing, which limits the transmission of data to within a specified region.
These SDKs support network geofencing in the following regions: global (default), North America, Europe, Asia (excluding Mainland China), Japan, India, and Mainland China. Once a customer specifies a region using geofencing, no audio, video, or message can access Agora servers outside that region.
### Security best practice checklist [#security-best-practice-checklist]
Use this list to quickly check what measures you have or have not taken to best protect the security of you app and users:
1. [Enable token-based authentication](./manage-agora-account.md) on [Agora Console](https://console.agora.io/v2).
2. Disable *No certificate* in your project management page. Once it is done your app authenticates users with tokens only.
3. [Deploy a token server](/en/realtime-media/video/build/authenticate-users/deploy-token-server) in your backend services.
4. Protect the token server and only allow the app backend server to connect to the token server.
5. Set the token expiration date to a reasonable time. See [Deploy a token server](/en/realtime-media/video/build/authenticate-users/deploy-token-server).
6. If needed, contact [support@agora.io](mailto\:support@agora.io) to enable token privilege and set token per role.
7. For additional security if needed work with the Agora SA team to customize the token server and modify the token join privilege to a low time, like 5 min. This is more advance and recommended after app development is completed.
8. Channel encryption key management – generate a random key (secret) per channel at the time of creating the channel, do not reuse encryption keys.
9. Pass the channel encryption key (secret) securely outside of Agora engine to authenticated endpoints that are allowed to join the channel.
10. For additional security, you can also do the following:
1. Set the “Agora channel name” to a one-time random string managed by your app.
2. Set the Agora `uid` to a one-time numeric id that is mapped to the user on the app level. Do not use the users' real ID on your app as the Agora `uid`.
## Agora bug bounty program [#agora-bug-bounty-program]
Agora is always looking to better protect our system and customers. Therefore, we invite security researchers to report any bugs or vulnerabilities they discover to us.
Once we receive notice of a bug or vulnerability, Agora customer service and security teams will respond quickly to address the issue.
In addition to our gratitude, those who report a vulnerability may be eligible for a monetary “bounty” based on the risk associated with the vulnerability and the importance of the affected system.
### System importance classifications [#system-importance-classifications]
The following table shows system importance classifications (in descending order), along with some example Agora assets:
| Classification | Asset Examples |
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Core system | Agora SDRTN®, the mail system, and official websites such as [www.agora.io](http://www.agora.io/), [sso.agora.io](http://sso.agora.io/), and `api.agora.io` |
| General system | Forums and the Developer Portal ([docs.agora.io](https://docs.agora.io/)) |
| Fringe system | Test sites |
### Vulnerability severity classifications [#vulnerability-severity-classifications]
The following list outlines detailed examples of how various vulnerabilities would be classified:
**Critical**
1. Unauthorized system privileges
Examples include command injection (execution), code injection (execution), web shell execution, SQL injection, and buffer overflow that gains system privileges on the core system.
2. Direct denial of service
Examples include actions that make service unavailable, reduce service quality, and so on.
3. Sensitive information leakage
Examples include SQL injection of the core database (identity, order), unauthorized disclosure of sensitive information relating to a user, product order, or payment method, and so on.
4. Serious logic design flaws and process defects
Examples include the ability to send batches of fraudulent messages, account consumption through a business interface, and large-scale modification of account passwords, and so on.
**High**
1. Sensitive information leakage
Examples include unauthorized disclosure of sensitive information relating to a source code, hardcoded passwords, and so on.
2. Unauthorized access to sensitive information
Examples include bypassing authentication or backend password, leading to unauthorized access to sensitive intranet information.
3. Unauthorized sensitive operations
Examples include manipulating important information without authorization, such as orders, major business configurations, and so on.
**Medium**
1. Vulnerabilities that require interaction and affect users
Examples include stored XSS (cross-site scripting).
2. General unauthorized operations
Examples include incorrect direct object references, unauthorized access to orders, unauthorized access to user information, and so on.
3. General information leakage
Examples include client-side stored plaintext passwords, system path traversal, and so on.
4. General logic design flaws and process defects
**Low**
1. Local denial of service vulnerabilities, CSRF (cross-site request forgery), reflected-XSS, and so on.
2. Minor information leakage, such as path information, SVN information, exception information, the local SQL injection of a client-side application (limited to database name, field name, log print), and so on.
3. Vulnerabilities that are difficult to exploit but still have security implications, such as plaintext transmission of passwords.
**Info**
1. Exposure of software banners, internal IP addresses, some public email addresses or phone numbers, and so on.
2. Using outdated versions of a system, supporting outdated version of an encryption protocol, such as SSL (secure-sockets layer) or tls (transport-layer security) 1.0, supporting low-strength encryption algorithms, and so on.
### Bug Bounties [#bug-bounties]
Please report any potential risks of Agora services that you may notice to [security@agora.io](mailto\:security@agora.io) . Any bounties awarded will conform roughly to the following ranges (based on the severity and system location of the bug or vulnerability, payable in $US):
* Critical: $300 to $5000
* High: $200 to $2000
* Medium: $150
* Low: $50
* Info: $0
Note: Agora reserves the sole right to determine any reward amount given.
# Media Gateway overview (/en/realtime-media/rtmp-gateway)
Agora's Media Gateway service enables publishing RTMP and SRT streams into Agora's real-time voice and video channels. By performing advanced transcoding at the edge, it reduces latency compared to traditional RTMP streaming and unlocks new possibilities for publishing and controlling real-time media streams.
Compatible with popular streaming tools, Media Gateway lets broadcasters maintain their existing setups while leveraging Agora SDRTN(R) for seamless and reliable delivery.
## Start building [#start-building]
## Product features [#product-features]
Enhance service reliability with a globally distributed cluster deployment, ensuring high-quality and seamless user experience in every region.
Experience seamless, real-time communication as the source stream directly enters the channel at the gateway, minimizing latency between the host and the audience.
Employ advanced transcoding capabilities on media streams for bandwidth optimization, enhanced delivery speed, cross-device compatibility, and wider distribution.
Simple and easy to use APIs help you integrate the feature quickly.
Safe and secure real-time transmission of audio and video data without caching or collecting users' personal information.
Works with several audio and video codecs, streaming protocols, and media formats.
# Quickstart (/en/realtime-media/rtmp-gateway/quickstart)
To push online media streams as live video source streams into Agora channels using Media Gateway, you need to obtain a server domain name and streaming key. Taking the OBS streaming software as an example, you configure the server's domain name and streaming key in the following way:

This page explains how to obtain the server domain name and generate a streaming key.
## Prerequisites [#prerequisites]
In order to follow this procedure you must:
* Have a project that implements an RTC product:
Interactive Live Streaming, Broadcast Streaming, Video Calling, or Voice Calling
* Generate app ID, app certificate, customer ID, and customer secret
* Pass basic HTTP or HMAC authentication
## Get server domain name [#get-server-domain-name]
You can use Agora's unified domain name or your own one. The server appends the `/live` suffix to the domain name.
* The Agora unified domain name is `rtls-ingress-prod-.agoramdn.com`. Replace `` with the code for your geographical region.
The supported regions and their codes are:
* `na`: North America
* `eu`: Europe
* `ap`: Asia, except Mainland China
* `cn`: Mainland China
* To use your own domain name, [contact technical support](mailto\:support@agora.io) for configuration.
## Get streaming key [#get-streaming-key]
The stream key generation method depends on whether you use the Agora domain name or a custom domain name.
* If you use Agora's domain name, create a stream key using the Media Gateway RESTful API.
* If you use your own domain name, you can either call the Media Gateway RESTful API or generate the key locally.
### Generate streaming key with RESTful API [#generate-streaming-key-with-restful-api]
Create and publish the streaming key by calling the following endpoint:
`PUT https://api.agora.io/:region/v1/projects/:appId/rtls/ingress/streamkeys`
For authentication details, see [RESTful authentication](./reference/restful-authentication.mdx).
To explore the RESTful API parameters, obtain sample code in various client languages, or test Media Gateway requests, refer to the [Postman API reference](https://documenter.getpostman.com/view/6319646/SVSLr9AM#6aed9690-285e-45f0-a329-c995adbd0956).
### Generate streaming key locally [#generate-streaming-key-locally]
Before starting, make sure you have configured your domain name by contacting [technical support](mailto\:support@agora.io).
To generate a stream key locally, you use the following information:
* The app ID of the Agora project from [Agora Console](https://console.agora.io/v2)
* The app certificate corresponding to your app ID
* `channelName`: The channel name
* `uid`: The user ID of the host in the channel
* `expiresAfter`: The effective duration of the stream key, in seconds
* `template` (optional): The associated flow configuration template
The following Node.js sample code demonstrates how to generate a stream key locally.
The following example code relies on the `msgpack-lite` module. If you haven't installed it yet, execute `npm install msgpack-lite` and run `node app.js` in the project root directory.
```javascript
const crypto = require('crypto');
const msgpack = require('msgpack-lite');
appcert = ""; // Your app certificate from Agora
channel = ""; // The Video SDK channel name
uid = ""; // The UID of the host in the channel
expiresAfter = 86400; // Valid duration of stream key (seconds)
const expiresAt = Math.floor(Date.now() / 1000) + expiresAfter;
const rtcInfo = {
C: channel,
U: uid,
E: expiresAt,
// If you are not sure whether to use the flow configuration template, keep the following line commented
// T: templateId,
};
const data = msgpack.encode(rtcInfo);
const iv = crypto.randomBytes(16);
const key = Buffer.from(appcert, 'hex');
const encrypter = crypto.createCipheriv('aes-128-ctr', key, iv);
const encrypted = Buffer.concat([iv, encrypter.update(data), encrypter.final()]);
const streamkey = encrypted
.toString('base64')
.replace(/\\+/g, '-')
.replace(/\\//g, '_')
.replace(/\\=+$/, '');
console.log(`streamkey is ${streamkey}`);
```
## Recommended config for web client communication [#recommended-config-for-web-client-communication]
In case of intercommunication with the web client, transcoding is not enabled by default. To ensure the best experience for web viewers, make sure that the streaming software uses the following encoding parameters:
* Key frame interval (GOP): `2s`
* Video profile: `baseline`
* x264 options: `threads=6`
* Frame rate (FPS): At 1080 resolution, the frame rate must not exceed 30; for resolutions below that, the frame rate must not exceed 60. If not necessary, 30 is sufficient.
Taking OBS as an example, configure it as shown below:
1. On the **Settings > Output > Live** page, configure the video profile, keyframe interval, and x264 options.

2. On the **Settings > Video** page, configure common frame rates.

## Next steps [#next-steps]
After completing the configuration, you can push RTMP or SRT streams to Agora channels, and these streams will be published to the corresponding channels by the host.
By default, after Media Gateway receives the pushed stream, it will not transcode it and will directly publish it to the Agora channel. If you want to transcode the streams, use stream configuration templates to implement related functions.
### REST API middleware [#rest-api-middleware]
[Agora Go Backend Middleware](https://github.com/AgoraIO-Community/agora-go-backend-middleware) is an open-source microservice that exposes a RESTful API designed to simplify Media Gateway interactions with Agora. Written in Golang and powered by the Gin framework, this community project serves as middleware to bridge front-end applications using Agora's Video SDK or Voice SDK with Agora's RESTful APIs.
# IoT & Edge (/en/realtime-media/rtsa)
## What this capability is [#what-this-capability-is]
RTSA provides high-quality realtime media streaming and signaling for hardware-oriented and terminal-style products that need low-latency connectivity.
## Common scenarios [#common-scenarios]
* smart cameras
* doorbells and wearables
* device-to-device media interop
* IoT realtime products
## What the existing Agora docs usually contain [#what-the-existing-agora-docs-usually-contain]
* landing page
* runnable examples
* quickstart
* popular capabilities
* best practices
## What to read first [#what-to-read-first]
Start from the landing page to evaluate fit, then run an example project, then add license, interop, and bitrate-control details.
# SDK extension plugins (/en/realtime-media/sdk-extensions)
## What this capability is [#what-this-capability-is]
SDK extension plugins sit on top of the core realtime SDK stack and help teams extend functionality or add richer experience layers.
## Common scenarios [#common-scenarios]
* customized audio-video effects
* extension-based capability integration
* fast access to third-party enhancement modules
## What to read first [#what-to-read-first]
If the main realtime path is already chosen and the next need is experience enhancement, extension plugins are the right place to continue.
# Speech-to-Text overview (/en/realtime-media/speech-to-text)
Agora's Real-Time Speech to Text transcribes live audio streams into text, enabling closed captions, live transcription, and AI-powered workflows. Translate transcribed text into multiple languages in real-time, or pass it directly to large language models to bridge real-time communication with intelligent applications.
## Quick links [#quick-links]
## Key features [#key-features]
Integrated with Agora’s voice and video service, live transcription and captions improve accessibility for your audience. Perfect for meetings, live streaming, lectures, interviews, live shopping, and more.
Break down language barriers with live speech-to-text translation to multiple languages during real-time communication or live streaming. The high accuracy translation text, delivered with ultra low latency, can be integrated with LLMs for enhanced capabilities.
Cloud-based service converts voice to text for active or specific hosts and then distributes the text to all participants in the channel for further processing. The service does not depend on the client's device performance and network conditions.
Label each transcribed text with the speaker's UID. Separate transcription of each host ensures accuracy even when multiple hosts are talking simultaneously.
Upload the transcriptions as .vtt files to cloud storage, then play back audio or video recordings with closed captions (CC). The timestamps in the .vtt file ensure that the text is perfectly synchronized with the audio or video, so that it appears exactly where it was generated.
Real-time transcription supports all major languages and dialects. Real-time translation supports translation into 40+ target languages.
# Cloud Transcoding overview (/en/realtime-media/transcoding)
Designed specifically for real-time interactive live broadcast scenarios, Agora's Cloud Transcoding service enables you to obtain audio and video source streams from hosts in RTC channels on the server side and perform processing such as transcoding, audio mixing, and video compositing. The processed streams are then published to Agora RTC channels for audience subscription.
The service supports subscriptions to audio and video streams with different picture quality levels, reducing downstream bandwidth pressure and client device performance consumption.
## Start building [#start-building]
## Product Features [#product-features]
Reduce terminal performance consumption by processing streams server-side before publishing to the RTC channel. This is especially beneficial for viewer devices with poor performance that cannot simultaneously process encoding, decoding, and rendering of multiple high-definition audio and video streams.
Improve video fluency and viewing experience under poor downlink network conditions. The service mixes audio, combines images, and transcodes them into low-bitrate streams, making it ideal for viewers subscribing to multiple host video streams or single HD streams.
Supports standard H.264 and VP8 encoding to enhance user experience across different platforms. The service converts host source streams from Native platforms to VP8 encoding for optimal Web viewer experience.
Automatically subscribe to specified user streams in RTC channels based on your UID configuration. The service performs transcoding, audio mixing, and video compositing according to your settings before publishing processed streams back to the channel.
Eliminate the need for viewers to subscribe to multiple host streams individually, reducing downstream bandwidth pressure. Scenario-based solutions lower the development threshold by requiring fewer API calls and reducing cognitive burden.
# Agora MCP (/en/realtime-media/transcoding/mcp)
The Agora MCP server gives your AI assistant direct access to Agora documentation, so it can look up APIs, SDK methods, and platform details in real time.
The server is available at:
```text
https://mcp.agora.io
```
## Installation [#installation]
Codex
Claude Code
Gemini CLI
```bash
codex mcp add --url https://mcp.agora.io agora-docs
```
```bash
claude mcp add --transport http agora-docs https://mcp.agora.io
```
```bash
gemini mcp add --transport http agora-docs https://mcp.agora.io
```
# REST quickstart (/en/realtime-media/transcoding/rest-quickstart)
This page explains how to call Agora Cloud Transcoding RESTful APIs to implement cloud transcoding in your app.
## Prerequisites [#prerequisites]
Before you start, ensure that you have followed the [Enable Cloud Transcoding](manage-agora-account) guide to:
* Set up your Agora account and activate the Cloud Transcoding service
* Obtain your App ID from Agora Console
* Obtain your Customer ID and Customer Secret for REST API authentication
* Generate RTC tokens for your channels (valid for up to 24 hours)
## Call a Cloud Transcoding REST API [#call-a-cloud-transcoding-rest-api]
This section walks you through creating a simple Cloud Transcoding task using the [Create RESTful API](/en/api-reference/cloud-transcoding/restful).
### Configure the request URL [#configure-the-request-url]
Each RESTful API request follows a specific format. For example, the URL to create a transcoding task is:
```html
https://api.sd-rtn.com/v1/projects/:appId/rtsc/cloud-transcoder/tasks?builderToken=
```
**URL structure:**
* `https`: Secure protocol.
* `api.sd-rtn.com`: Agora’s server domain.
* `v1/projects/:appId/rtsc/cloud-transcoder/tasks`: Resource path.
* `v1`: API version.
* `:appId`: Your Agora app ID.
* `rtsc/cloud-transcoder`: The product or service name.
* `tasks`: The endpoint you’re calling.
* `?builderToken=ACTUAL_TOKEN_VALUE`: Query parameter and its value
Obtain the `builderToken` using the [Acquire API](/en/api-reference/cloud-transcoding/restful) before creating a task.
Refer to the specific API documentation for the correct HTTP method (`GET`, `POST`, `PUT`, `PATCH`, `DELETE`) and resource path.
### Send the request [#send-the-request]
A typical API request includes:
* A URL
* Request headers
* (Optional) A request body
#### Request headers [#request-headers]
| Header | Description |
| --------------- | ------------------------------------------------------------------------------------------------------- |
| `Accept` | Expected response format, e.g., `application/json`. |
| `Authorization` | Required for authentication. See [RESTful Authentication](/en/api-reference/cloud-transcoding/restful). |
| `Content-Type` | Format of the request body, e.g., `application/json`. |
#### Request body [#request-body]
The request body contains the parameters specific to the API, such as input and output configurations.
#### Request example [#request-example]
```bash
curl --location --request POST 'https://api.sd-rtn.com/v1/projects//rtsc/cloud-transcoder/tasks?builderToken=' \
--header 'Accept: application/json' \
--header 'Authorization: Basic '
--header 'Content-Type: application/json' \
--data '{
"services": {
"cloudTranscoder": {
"serviceType": "cloudTranscoderV2",
"config": {
"transcoder": {
"audioInputs": [
{
"rtc": {
"rtcChannel": "test01",
"rtcUid": 123,
"rtcToken": "aab8b8f5a8cd4469a63042fcfafe7***"
}
},
{
"rtc": {
"rtcChannel": "test01",
"rtcUid": 456,
"rtcToken": "aab8b8f5a8cd4469a63042fcfafe7***"
}
}
],
"canvas": {
"width": 1280,
"height": 720,
"color": 255
},
"videoInputs": [
{
"rtc": {
"rtcChannel": "test01",
"rtcUid": 123,
"rtcToken": "aab8b8f5a8cd4469a63042fcfafe7***"
},
"placeholderImageUrl": "https://example.jpg",
"region": {
"x": 0,
"y": 0,
"width": 480,
"height": 360,
"zOrder": 2
}
},
{
"rtc": {
"rtcChannel": "test01",
"rtcUid": 456,
"rtcToken": "aab8b8f5a8cd4469a63042fcfafe7***"
},
"placeholderImageUrl": "https://example.jpg",
"region": {
"x": 0,
"y": 240,
"width": 480,
"height": 360,
"zOrder": 2
}
}
],
"outputs": [
{
"rtc": {
"rtcChannel": "test02",
"rtcUid": 1000,
"rtcToken": "aab8b8f5a8cd4469a63042fcfafe7***"
},
"audioOption": {
"profileType": "AUDIO_PROFILE_MUSIC_STANDARD"
},
"videoOption": {
"fps": 15,
"codec": "H264",
"width": 1280,
"height": 720
}
}
]
}
}
}
}
}'
```
This example mixes audio and combines video from users `123` and `456` in channel `test01`, and outputs the stream to `test02` with user ID `1000`.
### Handling the response [#handling-the-response]
After sending a request, the server responds with:
* HTTP status code
* Response headers
* Response body
#### Response status codes [#response-status-codes]
| Code | Meaning | Action |
| ------- | ------- | --------------------------------------------------- |
| 200 | Success | Proceed as normal. |
| Non-200 | Error | See [HTTP status codes](../reference/status-codes). |
#### Response headers (Optional) [#response-headers-optional]
Common response headers include:
* `X-Request-ID`: Unique request identifier for tracking/debugging.
#### Example response [#example-response]
```json
{
"taskId": "609f28f2644f1ae1ceb041b7047e3***",
"createTs": 1661324613,
"status": "STARTED",
"services": {
"cloudTranscoder": {
"serviceType": "cloudTranscoderV2",
"status": "serviceReady"
}
}
}
```
**Key fields:**
* `taskId`: Use this to query task status or stop the transcoding task
* `status`: Current task state
### Troubleshoot [#troubleshoot]
If a request fails:
1. **Check authentication**: Verify your Customer ID and Customer Secret are correct
2. **Validate parameters**: Ensure all required fields are included and properly formatted
3. **Check rate limits**: Ensure you're not exceeding 10 requests per second
4. **Inspect the response**: Use developer tools to examine the full error response
5. **Check status codes**: Refer to [HTTP status codes](../reference/status-codes) for specific error meanings
For persistent issues, contact [technical support](mailto\:support@agora.io) with your `X-Request-ID`.
# SDK Quickstart (/en/realtime-media/transcoding/sdk-quickstart)
This article explains how to use the Agora Go SDK to start cloud transcoding. The Go SDK helps developers integrate Agora's RESTful API more easily. It offers the following features:
* **Simplified communication**: The SDK encapsulates RESTful API requests and responses to simplify communication.
* **Ensured availability**: If DNS resolution fails, network errors occur, or requests time out, the SDK automatically switches to the optimal domain to ensure REST service availability.
* **Easy-to-use API**: The SDK provides a simple and intuitive API that makes it easy to perform common tasks such as creating and destroying cloud transcoding sessions.
* **Additional benefits**: Built with Go, the SDK offers efficiency, concurrency, and scalability.
## Understand the tech [#understand-the-tech]
When you use the Go SDK for Cloud Transcoding:
1. Call `Acquire()` to get a builder token for your transcoding task
2. Call `Create()` with your configuration to start transcoding specified streams
3. The service processes and publishes the transcoded streams to your target channels
4. Viewers can subscribe to either original or transcoded streams
The following sections show you how to implement this workflow.
## Prerequisites [#prerequisites]
Before you start, ensure that you have:
* [Go](https://go.dev/dl/) 1.18 or higher
* Followed the [Enable Cloud Transcoding](./build/manage-agora-account) guide to:
* Set up your Agora account and activate the Cloud Transcoding service
* Obtain your App ID from Agora Console
* Obtain your Customer ID and Customer Secret for REST API authentication
* Generate RTC tokens for your channels (valid for up to 24 hours)
* A way to test transcoding input streams:
* Implement the [Video Calling Quickstart](/en/realtime-media/video/get-started-sdk), or
* Use the Agora [Web Demo](https://webdemo-global.agora.io/index.html) to simulate audio and video streams
## Set up your project [#set-up-your-project]
Follow these steps to create and configure a new project:
1. Create an empty project folder named `test-transcoder`.
2. Navigate to the `test-transcoder` directory and initialize a Go module:
```bash
go mod init test-transcoder
```
3. Create a `main.go` file in the project directory for implementing your cloud transcoding task.
4. Install the Go SDK:
```bash
# Install the Go SDK
go get -u github.com/AgoraIO-Community/agora-rest-client-go
# Update dependencies
go mod tidy
```
5. Add the required imports to `main.go`:
```go
package main
import (
"context"
"log"
"time"
"github.com/AgoraIO-Community/agora-rest-client-go/agora"
"github.com/AgoraIO-Community/agora-rest-client-go/agora/auth"
"github.com/AgoraIO-Community/agora-rest-client-go/agora/domain"
agoraLogger "github.com/AgoraIO-Community/agora-rest-client-go/agora/log"
"github.com/AgoraIO-Community/agora-rest-client-go/services/cloudtranscoder"
cloudTranscoderAPI "github.com/AgoraIO-Community/agora-rest-client-go/services/cloudtranscoder/api"
)
```
### Implement cloud transcoding [#implement-cloud-transcoding]
This section introduces the minimal workflow for cloud transcoding. For demonstration purposes, this example only transcodes the host's audio.
### Define variables [#define-variables]
In the `main.go`, add the following code to define and configure the key parameters.
```go
// Define key parameters
const (
appId = ""
token = "" // Token for the transcoder in the input channel
username = "" // Your customer ID
password = "" // Your customer secret
inputChannelName = "show" // Channel name for the input stream
inputUId = 0 // User ID for the input stream
inputToken = "" // Token for the input stream user
outputChannelName = "show" // Channel name for the output stream
outputUId = 0 // User ID for the transcoder in the output channel
outputToken = "" // Token for the transcoder in the output channel
instanceId = "quickstart" // Instance ID of the transcoder
)
```
* In this example, the stream from the host of the `show` channel is used as the input stream for the transcoder. To simplify parameter configuration, the transcoder's output stream is also published to the same channel. The input and output streams share the same token.
* For Cloud Transcoding, multiple input streams must come from the same channel, while the output can be one or more streams and may be assigned to any channel.
### Create and initialize the client [#create-and-initialize-the-client]
In `main.go`, add the following code to create and initialize the client.
```go
config := &agora.Config{
AppID: appId,
Credential: auth.NewBasicAuthCredential(username, password),
// Specify the region where the server is located. Options include US, EU, AP, CN.
// The client automatically switches to use the best domain based on the configured region.
DomainArea: domain.US,
// Specify the log output level. Options include DebugLevel, InfoLevel, WarningLevel, ErrLevel.
// To disable log output, set logger to DiscardLogger.
Logger: agoraLogger.NewDefaultLogger(agoraLogger.DebugLevel),
}
client, err := cloudtranscoder.NewClient(config)
if err != nil {
log.Fatal(err)
}
```
### Get cloud transcoding resources [#get-cloud-transcoding-resources]
Before creating a cloud transcoding task, call the `Acquire` method to obtain a builder token `tokenName`. A token can only be used for a single cloud transcoding task.
```go
// Call the Acquire API of the cloud transcoder service client
acquireResp, err := client.Acquire(context.TODO(), &cloudTranscoderAPI.AcquireReqBody{
InstanceId: instanceId,
})
if err != nil {
log.Fatalln(err)
}
if acquireResp.IsSuccess() {
log.Printf("acquire success:%+v\n", acquireResp)
} else {
log.Fatalf("acquire failed:%+v\n", acquireResp)
}
tokenName := acquireResp.SuccessResp.TokenName
if tokenName == "" {
log.Fatalln("tokenName is empty")
}
log.Printf("tokenName:%s\n", tokenName)
```
### Start cloud transcoding [#start-cloud-transcoding]
Call `Create` to create a Cloud Transcoding task and start transcoding. In this example, the audio and video streams of two hosts in the same channel are mixed and combined.
```go
// Create a cloud transcoding task and start cloud transcoding
createResp, err := client.Create(context.TODO(), tokenName, &cloudTranscoderAPI.CreateReqBody{
Services: &cloudTranscoderAPI.CreateReqServices{
CloudTranscoder: &cloudTranscoderAPI.CloudTranscoderPayload{
ServiceType: "cloudTranscoderV2",
Config: &cloudTranscoderAPI.CloudTranscoderConfig{
Transcoder: &cloudTranscoderAPI.CloudTranscoderConfigPayload{
IdleTimeout: 300,
AudioInputs: []cloudTranscoderAPI.CloudTranscoderAudioInput{
{
Rtc: &cloudTranscoderAPI.CloudTranscoderRtc{
RtcChannel: inputChannelName,
RtcUID: inputUId,
RtcToken: inputToken,
},
},
},
Outputs: []cloudTranscoderAPI.CloudTranscoderOutput{
{
Rtc: &cloudTranscoderAPI.CloudTranscoderRtc{
RtcChannel: outputChannelName,
RtcUID: outputUId,
RtcToken: outputToken,
},
AudioOption: &cloudTranscoderAPI.CloudTranscoderOutputAudioOption{
ProfileType: "AUDIO_PROFILE_MUSIC_STANDARD",
},
},
},
},
},
},
},
})
if err != nil {
log.Fatalln(err)
}
if createResp.IsSuccess() {
log.Printf("create success:%+v\n", createResp)
} else {
log.Printf("create failed:%+v\n", createResp)
return
}
taskId := createResp.SuccessResp.TaskID
if taskId == "" {
log.Fatalln("taskId is empty")
}
log.Printf("taskId:%s\n", taskId)
// Wait for 10 seconds before stopping the cloud transcoding
time.Sleep(time.Second * 10)
```
### Stop cloud transcoding [#stop-cloud-transcoding]
When the transcoding task is complete, call the `Delete` method to end cloud transcoding:
```go
// Stop the cloud transcoding task
deleteResp, err := client.Delete(context.TODO(), taskId, tokenName)
if err != nil {
log.Println(err)
return
}
if deleteResp.IsSuccess() {
log.Printf("delete success:%+v\n", deleteResp)
} else {
log.Printf("delete failed:%+v\n", deleteResp)
return
}
```
### Complete example code [#complete-example-code]
The complete sample code for this example is presented here for your reference and use.
**Complete sample code**
```go
package main
import (
"context"
"log"
"time"
"github.com/AgoraIO-Community/agora-rest-client-go/agora"
"github.com/AgoraIO-Community/agora-rest-client-go/agora/auth"
"github.com/AgoraIO-Community/agora-rest-client-go/agora/domain"
agoraLogger "github.com/AgoraIO-Community/agora-rest-client-go/agora/log"
"github.com/AgoraIO-Community/agora-rest-client-go/services/cloudtranscoder"
cloudTranscoderAPI "github.com/AgoraIO-Community/agora-rest-client-go/services/cloudtranscoder/api"
)
// Define key parameters
const (
appId = ""
token = "" // Token for the transcoder in the input channel
username = "" // Your customer ID
password = "" // Your customer secret
inputChannelName = "show" // Channel name for the input stream
inputUId = 0 // User ID for the input stream
inputToken = "" // Token for the input stream user
outputChannelName = "show" // Channel name for the output stream
outputUId = 0 // User ID for the transcoder in the output channel
outputToken = "" // Token for the transcoder in the output channel
instanceId = "quickstart" // Instance ID of the transcoder
)
func main() {
// Initialize Agora Config
config := &agora.Config{
AppID: appId,
Credential: auth.NewBasicAuthCredential(username, password),
// Specify the region where the server is located. Options include CN, EU, AP, US.
// The client will automatically switch to use the best domain based on the configured region.
DomainArea: domain.CN,
// Specify the log output level. Options include DebugLevel, InfoLevel, WarningLevel, ErrLevel.
// To disable log output, set logger to DiscardLogger.
Logger: agoraLogger.NewDefaultLogger(agoraLogger.DebugLevel),
}
client, err := cloudtranscoder.NewClient(config)
if err != nil {
log.Fatal(err)
}
// Call the Acquire API of the cloud transcoder service client
acquireResp, err := client.Acquire(context.TODO(), &cloudTranscoderAPI.AcquireReqBody{
InstanceId: instanceId,
})
if err != nil {
log.Fatalln(err)
}
if acquireResp.IsSuccess() {
log.Printf("acquire success:%+v\n", acquireResp)
} else {
log.Fatalf("acquire failed:%+v\n", acquireResp)
}
tokenName := acquireResp.SuccessResp.TokenName
if tokenName == "" {
log.Fatalln("tokenName is empty")
}
log.Printf("tokenName:%s\n", tokenName)
// Create a cloud transcoding task and start cloud transcoding
createResp, err := client.Create(context.TODO(), tokenName, &cloudTranscoderAPI.CreateReqBody{
Services: &cloudTranscoderAPI.CreateReqServices{
CloudTranscoder: &cloudTranscoderAPI.CloudTranscoderPayload{
ServiceType: "cloudTranscoderV2",
Config: &cloudTranscoderAPI.CloudTranscoderConfig{
Transcoder: &cloudTranscoderAPI.CloudTranscoderConfigPayload{
IdleTimeout: 300,
AudioInputs: []cloudTranscoderAPI.CloudTranscoderAudioInput{
{
Rtc: &cloudTranscoderAPI.CloudTranscoderRtc{
RtcChannel: inputChannelName,
RtcUID: inputUId,
RtcToken: inputToken,
},
},
},
Outputs: []cloudTranscoderAPI.CloudTranscoderOutput{
{
Rtc: &cloudTranscoderAPI.CloudTranscoderRtc{
RtcChannel: outputChannelName,
RtcUID: outputUId,
RtcToken: outputToken,
},
AudioOption: &cloudTranscoderAPI.CloudTranscoderOutputAudioOption{
ProfileType: "AUDIO_PROFILE_MUSIC_STANDARD",
},
},
},
},
},
},
},
})
if err != nil {
log.Fatalln(err)
}
if createResp.IsSuccess() {
log.Printf("create success:%+v\n", createResp)
} else {
log.Printf("create failed:%+v\n", createResp)
return
}
taskId := createResp.SuccessResp.TaskID
if taskId == "" {
log.Fatalln("taskId is empty")
}
log.Printf("taskId:%s\n", taskId)
// Wait for 10 seconds before stopping the cloud transcoding
time.Sleep(time.Second * 10)
// Stop the cloud transcoding
deleteResp, err := client.Delete(context.TODO(), taskId, tokenName)
if err != nil {
log.Println(err)
return
}
if deleteResp.IsSuccess() {
log.Printf("delete success:%+v\n", deleteResp)
} else {
log.Printf("delete failed:%+v\n", deleteResp)
return
}
}
```
## Test your implementation [#test-your-implementation]
Follow these steps to test Cloud Transcoding:
1. In the project folder, run the Go project:
```bash
go run main.go
```
2. Check the console output for successful execution:
* Look for "acquire success" followed by a tokenName
* Look for "create success" followed by a taskId
* After 10 seconds, look for "delete success"
3. To test the transcoded audio output:
* Open the [Agora Web Demo](https://webdemo-global.agora.io/index.html)
* Join the same channel you configured
* You hear audio transcoded by your Go application
If you see error messages, check that your App ID, tokens, and credentials are correct.
## Reference [#reference]
This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.
* Refer to the [API documentation](/en/api-reference/cloud-transcoding/restful) for complete parameter details.
* If you encounter any problems, refer to [Status and error codes](./reference/status-codes).
### Sample project [#sample-project]
For more Cloud Transcoding examples, see the [Sample project on GitHub](https://github.com/AgoraIO-Community/agora-rest-client-go/tree/main/examples/cloudtranscoder).
# Agora skills (/en/realtime-media/transcoding/skills)
Agora skills are structured reference files that help AI coding assistants generate product-aware Agora code without guessing.
Skills include the [Agora MCP server](./mcp), which provides live access to the latest Agora documentation.
## Installation [#installation]
```bash
npx skills add github:AgoraIO/skills
```
To install manually:
```bash
git clone https://github.com/AgoraIO/skills.git ~/agora-skills
```
Point your assistant to `skills/agora/` and use `SKILL.md` as the entry point.
# Core concepts (/en/realtime-media/video/core-concepts)
RTC (Real-Time Communication) refers to real-time communication technology, which allows almost instant exchange of audio, video, and other data between the sender and the receiver.
Agora SDKs provide real-time audio and video interaction services, with multi-platform and multi-device support. This includes high-definition video calls, voice-only calls, interactive live streaming, as well as one-on-one and multi-group chats.
This guide introduces the key processes and concepts you need to know to use Video SDK.
Agora relies on the following fundamental concepts to enable seamless real-time communication:
## Agora SDRTN® [#agora-sdrtn]
Agora's core engagement services are powered by its Software-Defined Real-Time Network (SDRTN®), a global infrastructure accessible anytime, anywhere. Unlike traditional networks, Agora SDRTN® is not restricted by devices, phone numbers, or telecom coverage areas. With data centers in over 200 countries and regions, it ensures sub-second latency and high availability for real-time media.
Agora SDRTN® enables live user engagement through real-time communication (RTC), offering:
* Unmatched quality of service
* High availability and accessibility
* True scalability
* Low cost
## Channel concepts [#channel-concepts]
Agora uses channels to group users together, enabling seamless communication and interaction. Channels serve as the foundation for transmitting real-time data, whether audio, video, or signaling, and play a crucial role in connecting users and services.
### Channel [#channel]
A channel organizes users into a group and is identified by a unique channel name. Users who connect to the same channel are able to communicate with each other. A channel is created when the first user joins and ceases to exist when the last user leaves.
Channels are created by calling the methods for transmitting real-time data. Agora uses different channels to transmit different types of data:
* A Video SDK channel is used for transmitting audio or video data.
* A Signaling channel is used for transmitting messaging or signaling data.
These channels are independent of each other.
Additional services provided by Agora, such as Cloud Recording and STT, join the Video SDK channel to provide real-time recording, transmission acceleration, media playback, and content moderation.
### Channel profile [#channel-profile]
The Video SDK applies different optimization methods according to the selected channel profile. Agora supports the following channel profiles:
| Channel profile | Description |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Communication** | This profile is suitable for one-on-one or group calls, where all users in the channel talk freely. |
| **Live Broadcasting** | In a live streaming channel, users have two client roles: *host* and *audience*. The *host* sends and receives streams, while the *audience* only receives streams with the sending function disabled. |
### Stream [#stream]
A stream is a sequence of digitally encoded, coherent signals that contain media data. Users in a channel [publish](#publish) local streams and [subscribe](#subscribe) to remote streams from other users.
### User role [#user-role]
The user role defines whether a user in a channel has the permission to publish streams. There are two user roles:
* **Host**: A user who can publish streams to a channel.
* **Audience**: A User who can only subscribe to remote media streams. A user with this role cannot publish streams.
### Publish [#publish]
Publishing is the act of sending a user’s audio or video data to the channel. Usually, the published stream is created by the audio data sampled from a microphone or the video data captured by a camera. You can also publish media streams from other sources, such as an online music file or the user’s screen.
After successfully publishing a stream, the SDK uses it to send media data to other users in the channel. Users communicate with each other in real-time by publishing local streams and subscribing to remote streams.
### Subscribe [#subscribe]
Subscribing is the act of receiving media streams published by remote users to the channel. A user receives audio and video data from other users by subscribing to one or more of their streams. You either directly play the subscribed streams or process incoming data for other purposes such as recording or capturing screenshots.
### User ID [#user-id]
In Video Calling, the UID is an integer value that uniquely identifies a user within the context of a channel. When joining a channel, you have the option to either assign a specific UID to the user or pass `0` or `null` and allow Agora to automatically generate and assign a UID to the user. If two users attempt to join the same channel with the same UID, it can lead to unexpected behavior.
The UID is used by Agora's services and components to identify and manage users within a channel. Ensure that UIDs are properly assigned to prevent conflicts.
### RTC connection [#rtc-connection]
The connection between the SDK and the channel. When publishing or subscribing to multiple streams in multiple channels, a connection is used to specify the target channel.
## Credentials [#credentials]
To ensure reliable access and secure communication, Agora uses credentials such as the App ID, App Certificate, and tokens to identify applications, authenticate their requests, and authorize their access on its platform.
### App ID [#app-id]
The App ID is a unique key generated by Agora to identify each project and provide billing and other statistical data services. The App ID is critical for connecting users within your app. It is used to initialize the Agora Engine in your app, and as one of the required keys to create authentication tokens for secure communication. Retrieve the App ID for your project using the [Agora Console](https://console.agora.io/v2/project-management).
App IDs are stored on the front-end client and do not provide access control. Projects using only an App ID allow any user with the App ID to join. For access control, especially in production environments, choose the **App ID + Token** mechanism for user authentication when creating a new project. Without authentication tokens, your environment is open to anyone with access to your App ID.
### App Certificate [#app-certificate]
An App Certificate is a unique key generated by the Agora Console to secure projects through token authentication. It is required, along with the App ID, to generate a token that proves authorization between your systems and Agora's network. App Certificates are used to generate Video Calling authentication tokens.
Store the App Certificate securely in your backend systems. If your App Certificate is compromised or to meet security compliance requirements, you can invalidate certificates and create new ones through the Agora Console.
### Tokens [#tokens]
A token is a dynamic key generated using the App ID, App Certificate, user ID, and expiration timestamp. Tokens authenticate and secure access to Agora's services, ensuring only authorized users can join a channel and participate in real-time communication.
Tokens are generated on your server and passed to the client for use in Video Calling. The token generation process involves digitally signing the App ID, App Certificate, user ID, and expiration timestamp using a specific algorithm, preventing tampering or forgery.
During development and testing, use the Agora Console to generate temporary tokens. For production environments, implement a token server as part of your security infrastructure to control access to your channels.
For information on setting up a token server for generating and managing tokens, refer to [Deploy a token server](/en/realtime-media/video/build/authenticate-users/deploy-token-server).
### Agora Console [#agora-console]
[Agora Console](https://console.agora.io/v2) is the main dashboard where you manage your Agora projects and services. Before you can use Agora's SDKs, you must first create a project in the Agora Console. See [Agora account management](/en/realtime-media/video/manage-agora-account) for
details.

Agora Console provides an intuitive interface for developers to query and manage their Agora account. After registering an Agora account, you use the Agora Console to perform the following tasks:
* Manage your account
* Create and configure Agora projects and services
* Get an App ID and the App certificate
* Generate temporary tokens for development and testing
* Manage members and roles
* Check call quality and usage
* Check bills and make payments
* Access product resources
See [Agora account management](/en/realtime-media/video/manage-agora-account) for details on how to manage all aspects of your Agora account.
Agora also provides RESTful APIs that you use to implement features such as creating a project and fetching usage numbers programmatically.
## Audio and video concepts [#audio-and-video-concepts]
### Audio and video interaction workflow [#audio-and-video-interaction-workflow]
The following figure illustrates the workflow of using the Video SDK to implement basic audio and video interaction.

Agora relies on the following fundamental concepts to enable seamless real-time communication:
### Track [#track]
A track contains specific audio or video information. It consists of three parts: input source, filter, and output. According to different functions in the RTC process, tracks can be further classified into uplink tracks and downlink tracks.

#### Input source [#input-source]
The input source is the local audio or video data to be published. It can be from a camera, screen capture, or microphone source, or parsed from a media file.
#### Filter [#filter]
A filter performs a series of processing operations on audio and video, including pre-processing and post-processing, and transmits the processed audio and video signals to the output. Filters can be connected to multiple input sources or multiple outputs.
* Pre-processing: Audio/video filters in the sender track, such as virtual background, beautification, echo cancellation, and noise reduction.
* Post-processing : Audio/video filters in the receiving track, such as super-resolution, and spatial audio effects.
#### Output [#output]
Located at the end of the track, such as an encoder, or a renderer.
### Audio module [#audio-module]
In audio interaction, the main functions of the audio module are as shown in the figure below:

After you call `registerAudioFrameObserver`, you can obtain the raw audio data at the following observation points in the audio transmission process:
1. Obtain the raw audio data of ear monitoring through the `onEarMonitoringAudioFrame` callback.
2. Obtain the captured raw audio data through the `onRecordAudioFrame` callback.
3. Obtain the raw audio playback data of each individual stream through the `onPlaybackAudioFrameBeforeMixing` callback.
4. Obtain the raw audio playback data of all mixed streams through the `onPlaybackAudioFrame` callback.
5. Obtain the raw audio data after mixing the captured and playback audio through the `onMixedAudioFrame` callback.
(5) `onMixedAudioFrame` = (2) `onRecordAudioFrame` + (4) `onPlaybackAudioFrame`
### Audio routing [#audio-routing]
The audio output device used by the app when playing audio. Common audio routes include wired headphones, earpieces, speakers, Bluetooth headphones, and others.
The APIs used by the audio module are as follows:
* Enable local audio collection: `enableLocalAudio`
* Set local playback device: `setPlaybackDevice`
* Set up audio routing: `setDefaultAudioRouteToSpeakerphone`
### Video module [#video-module]
The following diagram shows the main functions of the video module in video interaction:

The figure shows the following observation points:
1. `POSITION_POST_CAPTURER_ORIGIN`.
2. `POSITION_POST_CAPTURER`, corresponds to the `onCaptureVideoFrame` callback.
3. `POSITION_PRE_ENCODER`, corresponds to the `onPreEncodeVideoFrame` callback.
4. `POSITION_PRE_RENDERER`, corresponds to the `onRenderVideoFrame` callback.
The APIs used by the video module are as follows:
* Enable local video collection: `enableLocalVideo`
* Local preview: `setupLocalVideo` → `startPreview`
* Video rendering shows: `setupRemoteVideo`
# Quickstart (/en/realtime-media/video/get-started-sdk)
<_PlatformTabsGroup groupMode="structured" canonicalPlatform="web" platforms="["android","ios","macos","web","windows","electron","flutter","react-native","javascript","unity","unreal","blueprint"]" 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.

## 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]
Create a new project
Add to an existing project
1. Create a [new project](https://developer.android.com/studio/projects/create-project).
1. Open Android Studio and select **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.
Note
After you create a project, Android Studio automatically starts gradle sync. Ensure that the synchronization is successful before proceeding to the next step.
1. Add a new activity to your project.
1. Open your project in Android Studio.
2. Right-click on the `app/src/main/java/` 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.
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.
Maven Central
Manual download
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()
}
```
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.
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`.
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.
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.**
```
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.**
```
## 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:

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:
Java
Kotlin
```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;
```
```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
```
### 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:
Java
Kotlin
```java
// Fill in the app ID from Agora Console
private String myAppId = "";
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());
}
}
```
```kotlin
// Fill in the App ID obtained from the Agora Console
private val myAppId = ""
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}")
}
}
```
### 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`.
Java
Kotlin
```java
// Fill in the channel name
private String channelName = "";
// Fill in the temporary token generated from Agora Console
private String 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);
}
```
```kotlin
// Fill in the channel name
private val channelName = ""
// Fill in the temporary token generated from Agora Console
private val 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)
}
```
### 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.
To ensure that you receive all Video SDK events, set the engine event handler before joining a channel.
Java
Kotlin
```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);
});
}
};
```
```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")
}
}
}
```
### 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.
Java
Kotlin
```java
private void enableVideo() {
mRtcEngine.enableVideo();
mRtcEngine.startPreview();
}
```
```kotlin
private fun enableVideo() {
mRtcEngine?.apply {
enableVideo()
startPreview()
}
}
```
### Display the local video [#display-the-local-video]
Call `setupLocalVideo` to initialize the local view and set the local video display properties.
Java
Kotlin
```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));
}
```
```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))
}
```
### 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.
Java
Kotlin
```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));
}
```
```kotlin
private fun setupRemoteVideo(uid: Int) {
val container = findViewById(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))
}
```
### 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 ``:
```xml
```
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:
Java
Kotlin
```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();
}
}
```
```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 {
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,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == PERMISSION_REQ_ID && checkPermissions()) {
startVideoCalling()
}
}
```
### 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.
Java
Kotlin
```java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (checkPermissions()) {
startVideoCalling();
} else {
requestPermissions();
}
}
```
```kotlin
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (checkPermissions()) {
startVideoCalling()
} else {
requestPermissions()
}
}
```
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.
Java
Kotlin
```java
private void cleanupAgoraEngine() {
if (mRtcEngine != null) {
mRtcEngine.stopPreview();
mRtcEngine.leaveChannel();
mRtcEngine = null;
}
}
```
```kotlin
private fun cleanupAgoraEngine() {
mRtcEngine?.apply {
stopPreview()
leaveChannel()
}
mRtcEngine = null
}
```
### 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 `` in package `com.example.` with your project's name.
Java
Kotlin
```java
package com.example.;
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 = "";
private String channelName = "";
private String 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());
}
}
```
```kotlin
package com.example.
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 = ""
private val channelName = ""
private val 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 {
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,
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()
}
}
}
```
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.
### 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.

### Sample code to create the user interface [#sample-code-to-create-the-user-interface]
```xml
```
## 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  **Sync Project with Gradle Files** to resolve project dependencies and update the configuration.
4. After synchronization is successful, click  **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 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.

## 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]
Create a new project
Add to an existing project
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.
Information
If you have not added the development team information, you see the **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.
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.
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.
### Install the SDK [#install-the-sdk-1]
Use one of the following methods to install the Video SDK.
Swift Package Manager
CocoaPods
Manual integration
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).
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 **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.
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**.
Information
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.
Note
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.
## 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:

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 = "" // 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:
To ensure that you receive all Video SDK events, set the engine event handler before joining a channel.
```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. |
* 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).
### 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()
```
caution
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.
### 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:
```swift
import UIKit
import AgoraRtcKit
class ViewController: UIViewController {
let appId = "" // Replace with your actual App ID
let channelName = "demo" // Replace with your actual channel name
let 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)
}
}
```
### 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
NSPrivacyTrackingNSPrivacyCollectedDataTypesNSPrivacyAccessedAPITypesNSPrivacyAccessedAPITypeNSPrivacyAccessedAPICategorySystemBootTimeNSPrivacyAccessedAPITypeReasons35F9.1NSPrivacyAccessedAPITypeNSPrivacyAccessedAPICategoryFileTimestampNSPrivacyAccessedAPITypeReasonsDDA9.1NSPrivacyAccessedAPITypeNSPrivacyAccessedAPICategoryDiskSpaceNSPrivacyAccessedAPITypeReasonsE174.1
```
### 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 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.

## 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]
Create a new project
Add to an existing project
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.
Information
If you have not added the development team information, you see the **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.
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.
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.
### Install the SDK [#install-the-sdk-2]
Use one of the following methods to install the Video SDK.
Swift Package Manager
CocoaPods
Manual integration
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).
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 **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.
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**.
Information
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.
Note
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.
## 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:

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 = "" // 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:
To ensure that you receive all Video SDK events, set the engine event handler before joining a channel.
```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. |
* 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).
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()
```
caution
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.
### 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:
```swift
import Cocoa
import AgoraRtcKit
// ViewController.swift
class ViewController: NSViewController {
let appId = "" // Replace with your actual App ID
let channelName = "demo" // Replace with your actual channel name
let 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)
}
}
```
### 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 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.
## 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.

## 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]
Create a new project
Add to an existing project
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.
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
```
### 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:

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("");
}
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`.
To ensure that you receive all Video SDK events, set up event listeners before joining a channel.
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.
```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();
```
### 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
Web SDK Video Quickstart
Web SDK Video Quickstart
JoinLeave
```
## 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:

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.
Information
* 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`).
## 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+) |
* 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.
### 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:

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

## 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]
Create a new project
Add to an existing project
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**.
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.
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:

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.
To ensure that you receive all Video SDK events, set the engine event handler before joining a channel.
```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;
```
Caution
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).
### 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]
```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 ""
// Insert the temporary token obtained from the Agora Console
#define 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(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(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;
}
```
### 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:

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

## 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]
npm integration
Manual Integration
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.
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.
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
```
* 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.
Compile the latest SDK from the [Electron SDK repository](https://github.com/AgoraIO-Extensions/Electron-SDK/tree/main/) and integrate it into your application.
## 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:

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);
```
To ensure that you receive all Video SDK events, set the engine event handler before joining a channel.
### 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();
```
caution
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.
### 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]
```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,
});
};
```
Information
* 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).
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
Electron Quickstart
Electron Quickstart
```
## 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 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.

## 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]
Create a new project
Add to an existing project
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
```
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`.
### 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:

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 _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 _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.
To ensure that you receive all Video SDK events, register the event handler before joining a channel.
### 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 _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 _requestPermissions() async {
await [Permission.microphone, Permission.camera].request();
}
```
Information
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 |
### 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 _cleanupAgoraEngine() async {
await _engine.leaveChannel();
await _engine.release();
}
```
Warning
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.
### 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.
```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 {
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 _startVideoCalling() async {
await _requestPermissions();
await _initializeAgoraVideoSDK();
await _setupLocalVideo();
_setupEventHandlers();
await _joinChannel();
}
// Requests microphone and camera permissions
Future _requestPermissions() async {
await [Permission.microphone, Permission.camera].request();
}
// Set up the Agora RTC engine instance
Future _initializeAgoraVideoSDK() async {
_engine = createAgoraRtcEngine();
await _engine.initialize(const RtcEngineContext(
appId: appId,
channelProfile: ChannelProfileType.channelProfileCommunication,
));
}
// Enables and starts local video preview
Future _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 _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 _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,
);
}
}
}
```
Information
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.
### 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:

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

## 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:
npm
Yarn
In your project folder, execute the following command:
```bash
npm i --save react-native-agora
```
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
```
information
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
## 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:

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,',
});
}
};
```
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.
### 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);
};
```
To ensure that you receive all Video SDK events, register the event handler before joining a channel.
### 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
```
### 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
```
### Handle permissions [#handle-permissions-5]
To access the camera and microphone, ensure that the user grants the necessary permissions when the app starts.
Android
iOS
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,
]);
}
};
```
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. |
### 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();
};
};
```
After calling `release,` methods and callbacks of the SDK are no longer available. To use the SDK functions again, create a new engine instance.
### 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.
```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 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(); // 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 (
Agora Video SDK Quickstart
Join Channel
Leave Channel
Audience {
setIsHost(switchValue);
if (isJoined) {
leave();
}
}}
value={isHost}
/>
Host
{isJoined && isHost ? (
Local user uid: {localUid}
) : (
Join a channel
)}
{isJoined && remoteUid !== 0 ? (
Remote user uid: {remoteUid}
) : (
{isJoined && !isHost ? 'Waiting for remote user to join' : ''}
)}
{message}
);
// 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;
```
### 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 (
Agora Video SDK Quickstart
Join Channel
Leave Channel
Audience {
setIsHost(switchValue);
if (isJoined) {
leave();
}
}}
value={isHost}
/>
Host
{isJoined && isHost ? (
Local user uid: {localUid}
) : (
Join a channel
)}
{isJoined && remoteUid !== 0 ? (
Remote user uid: {remoteUid}
) : (
{isJoined && !isHost ? 'Waiting for remote user to join' : ''}
)}
{message}
);
};
// 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.
Caution
Some simulators may not support all the functions of this project. Best practice is to run the project on a physical device.
Android
iOS
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`.
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.
Information
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).
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 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.

React SDK and Web SDK relationship
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.
## 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:
npm
CDN
Navigate to the project folder and run the following command to install the Video SDK:
```bash
npm i agora-rtc-react
```
To integrate React JS dependencies and the Agora SDK using CDN, add the following code to your HTML file:
```html
```
## 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 `` component. Wrap the `` component with `` 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(
);
}
```
### 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
```
### 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) => (
{user.uid}
))}
```
### 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.
```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(
);
}
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 (
<>
)}
>
);
};
export default VideoCalling;
```
## 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.
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.
If `CodeSandbox` access is slow, try adjusting your network configuration.
## 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 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.

## 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]
Create a new project
Add to an existing project
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**.
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.
### 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:

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);
}
```
To ensure that you receive all Video SDK events, register the event handler before joining a channel.
### 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.
Android
iOS/macOS
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
}
```
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.
### 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;
}
}
```
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.
### 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]
```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();
go.transform.Rotate(0.0f, 0.0f, -180.0f);
go = GameObject.Find("RemoteView");
RemoteView = go.AddComponent();
go.transform.Rotate(0.0f, 0.0f, -180.0f);
go = GameObject.Find("Leave");
go.GetComponent().onClick.AddListener(Leave);
go = GameObject.Find("Join");
go.GetComponent().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");
}
}
}
```
### 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:

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

## 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]
Create a new project
Add to an existing project
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 **C++**.
* **Target Platform**: Select **Desktop**.
* **Project Location**: Enter the project files storage path.
* **Project Name**: Type a suitable name for your project.
Click **Create**.

1. In the Unreal Project Browser, click on **Browse** and locate the `.uproject` file.
2. Select the project and click **Open**.
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**.

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:

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:
Android
Apple
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 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
}
```
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" } );
}
}
```
### 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]
```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
}
}
```
### 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.

2. Create a view canvas.
Select **Palette > PANEL > Canvas Panel** and drag it to **AgoraWidget**.

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:

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

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:

4. Save your changes and run the project. The UI you have created looks similar to the following:

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

## 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]
Create a new project
Add to an existing project
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**.

1. In the Unreal Project Browser, click on **Browse** and locate the `.uproject` file.
2. Select the project and click **Open**.
### 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.

### 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:

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.
Information
* This node is available on Windows and macOS only.
* If the node is not retrieved at creation time, uncheck **Context Sensitive**. 
|
| 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.
Note
If your target platform is Android, create this node to check system permissions.
|
| 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.
\*\* 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.

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

Information
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)
2. To initialize the RTC engine, in the **InitRtcEngine** function, create and connect nodes as shown in the following figure:

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.

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.

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.

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.

Information
You can also bind UI events in Unreal Motion Graphics (UMG). This document only shows binding using the **Bind UIEvent Function**.
### 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:

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.

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):

2. After the local user leaves the channel, the `onLeaveChannel` callback is triggered which releases the local view:

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):

4. When a remote user leaves the channel, the `onUserOffline` callback is triggered to release the remote view:

### 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:

## 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" />
# Video Calling overview (/en/realtime-media/video)
Agora's Video Calling API delivers ultra-low-latency, high-definition real-time video communication for one-to-one and group calls. With support for cross-platform integration and dynamic resolution adaptation, it ensures smooth, reliable video experiences even under challenging network conditions. Use it for telehealth, online education, team collaboration, customer support, and any application requiring real-time face-to-face communication.
Enhance Agora's Video SDK with capabilities such as recording, virtual backgrounds, and in-call moderation, or leverage the Extensions Marketplace to enable AI-powered features like noise cancellation, video effects, and more.
## Start building [#start-building]
## Product Features [#product-features]
Agora’s Software-Defined Real-Time Network (SDRTN®) supports video users in over 200 countries and regions.
Consistent high-quality video from 1:1 calls to thousands of concurrent users, even under challenging network conditions.
Publish multiple audio and video tracks to one or more channels from a single instance, with support for multi-channel capture cameras and microphones.
Enable screen sharing or interactive whiteboards that allow users to draw, annotate, and share content from multiple devices simultaneously.
Support for high quality audio with 3D spatial audio, AI noise suppression, and gain control to provide an immersive audio experience.
Record video calls in the cloud or on premises with control over the format, path of storage, and quality.
# Agora account management (/en/realtime-media/video/manage-agora-account)
This page shows you how to sign up for an Agora account, create a new project, and get the app ID and app certificate to generate a temporary token.
## Get started with Agora [#get-started-with-agora]
To join a Video Calling session, you need an Agora App ID. This section shows you how to set up an Agora account, create an Agora project and get the required information from [Agora Console](https://console.agora.io/v2).
### Sign up for an Agora account [#sign-up-for-an-agora-account]
To use Agora products and services, create an Agora account with your email, phone number, or a third-party account.
Sign up with email
Sign up with a third-party account
1. Go to the [signup page](https://sso.agora.io/en/signup).
2. Fill in the required fields.
3. Carefully read the **Terms of Service**, **Privacy Policy**, and **Acceptable Use Policy**, and tick the checkbox.
4. Click **Continue**.
5. Enter your **verification code** and click **Confirm**.
6. Follow the on-screen instructions to provide your name, company name, and phone number, set a password, and click **Continue**.
1. On the [Agora Console](https://console.agora.io/v2) login page, select the third-party account you want to use.
2. Follow the on-screen instructions to complete verification.
3. Click **Create a new account**.
4. Carefully read the **Terms of Service**, **Privacy Policy**, and **Acceptable Use Policy**, and select the checkbox.
5. Click **Continue**.
Once you sign up successfully, your account is automatically logged in. Follow the on-screen instructions to create your first project and test out real-time communications.
For later visits, log in to [Agora Console](https://console.agora.io/v2) with your phone number, email address, or linked third-party account.
### Create an Agora project [#create-an-agora-project]
To create an Agora project, do the following:
1. In [Agora Console](https://console.agora.io/v2), open the [Projects](https://console.agora.io/v2/project-management) page.
2. Click **Create New**.
3. Follow the on-screen instructions to enter a project name and use case, and check **Secured mode: APP ID + Token (Recommended)** as the authentication mechanism.

4. Click **Submit**. You see the new project on the **Projects** page.
### Get the App ID [#get-the-app-id]
Agora automatically assigns a unique identifier to each project, called an App ID.
To copy this App ID, find your project on the [Projects](https://console.agora.io/v2/project-management) page in Agora Console, and click the copy icon in the **App ID** column.

## Security and authentication [#security-and-authentication]
Use the following features from your Agora account to implement security and authentication features in your apps.
### Get the App Certificate [#get-the-app-certificate]
When generating an authentication token on your app server, you need an App Certificate, in addition to the App ID.
To get an App Certificate, do the following:
1. On the [Projects](https://console.agora.io/v2/project-management) page, click the pencil icon to edit the project you want to use.

2. Click the copy icon under **Primary Certificate**.

### Generate temporary tokens [#generate-temporary-tokens]
To ensure communication security, best practice is to use tokens to authenticate the users who log in from your app.
To generate a temporary RTC token for use in your Video SDK projects:
1. On the [Projects](https://console.agora.io/v2/project-management) page, click the pencil icon next to your project.
2. On the **Security** panel, click **Generate Temp Token**, enter a channel name in the pop-up box and click **Generate**. Copy the generated RTC token for use in your Video Calling projects.
To generate a token for other Agora products:
1. In your browser, navigate to the [Agora token builder](https://agora-token-generator-demo.vercel.app/).
2. Choose the Agora product your user wants to log in to. Fill in **App ID** and **App Certificate** with the
details of your project in Agora Console.
3. Customize the token for each user. The required fields are visible in the Agora token builder.
4. Click **Generate Token**.
The token appears in Token Builder.
5. Copy the token and use it in your app.
For more information on managing other aspects of your Agora account, see [Agora console overview](/en/realtime-media/video/reference/console-overview).
# Agora MCP (/en/realtime-media/video/mcp)
The Agora MCP server gives your AI assistant direct access to Agora documentation, so it can look up APIs, SDK methods, and platform details in real time.
The server is available at:
```text
https://mcp.agora.io
```
## Installation [#installation]
Refer to the setup guide for your coding assistant.
Codex
Claude Code
Gemini CLI
Manual installation
```bash
codex mcp add --url https://mcp.agora.io agora-docs
```
```bash
claude mcp add --transport http agora-docs https://mcp.agora.io
```
```bash
gemini mcp add --transport http agora-docs https://mcp.agora.io
```
For Cursor and other tools, add `https://mcp.agora.io` as a remote MCP server and use `http` or Streamable HTTP transport when prompted.
# SDK quickstart (/en/realtime-media/video/quickstart)
<_PlatformTabsGroup groupMode="structured" canonicalPlatform="web" platforms="["android","ios","macos","web","windows","electron","flutter","react-native","javascript","unity","unreal","blueprint","python"]" 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.

## Prerequisites [#prerequisites]
## Set up your project [#set-up-your-project]
Create a new project
Add to an existing project
1. Create a [new project](https://developer.android.com/studio/projects/create-project).
1. Open Android Studio and select **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.
Note
After you create a project, Android Studio automatically starts gradle sync. Ensure that the synchronization is successful before proceeding to the next step.
1. Add a new activity to your project.
1. Open your project in Android Studio.
2. Right-click on the `app/src/main/java/` 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.
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.
Maven Central
Manual download
1. Open the `settings.gradle` file in the project's root directory and add the Maven Central dependency, if it doesn't already exist:
```text
repositories {
mavenCentral()
}
```
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.
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`
```text
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`.
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.
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.**
```
1. Download the latest version of Video SDK from the [SDKs](/en/api-reference/sdks?product=video\&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.**
```
## Implement Video Calling [#implement-video-calling]
The following figure illustrates the essential steps:
### Quick start sequence [#quick-start-sequence]

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:
Java
Kotlin
```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;
```
```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
```
### 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:
Java
Kotlin
```java
// Fill in the app ID from Agora Console
private String myAppId = "";
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());
}
}
```
```kotlin
// Fill in the App ID obtained from the Agora Console
private val myAppId = ""
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}")
}
}
```
### 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`.
Java
Kotlin
```java
// Fill in the channel name
private String channelName = "";
// Fill in the temporary token generated from Agora Console
private String 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);
}
```
```kotlin
// Fill in the channel name
private val channelName = ""
// Fill in the temporary token generated from Agora Console
private val 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)
}
```
### 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.
To ensure that you receive all Video SDK events, set the engine event handler before joining a channel.
Java
Kotlin
```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);
});
}
};
```
```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")
}
}
}
```
### 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.
Java
Kotlin
```java
private void enableVideo() {
mRtcEngine.enableVideo();
mRtcEngine.startPreview();
}
```
```kotlin
private fun enableVideo() {
mRtcEngine?.apply {
enableVideo()
startPreview()
}
}
```
### Display the local video [#display-the-local-video]
Call `setupLocalVideo` to initialize the local view and set the local video display properties.
Java
Kotlin
```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));
}
```
```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))
}
```
### 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.
Java
Kotlin
```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));
}
```
```kotlin
private fun setupRemoteVideo(uid: Int) {
val container = findViewById(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))
}
```
### 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 ``:
```xml
```
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:
Java
Kotlin
```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();
}
}
```
```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 {
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,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == PERMISSION_REQ_ID && checkPermissions()) {
startVideoCalling()
}
}
```
### 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.
Java
Kotlin
```java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (checkPermissions()) {
startVideoCalling();
} else {
requestPermissions();
}
}
```
```kotlin
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (checkPermissions()) {
startVideoCalling()
} else {
requestPermissions()
}
}
```
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.
Java
Kotlin
```java
private void cleanupAgoraEngine() {
if (mRtcEngine != null) {
mRtcEngine.stopPreview();
mRtcEngine.leaveChannel();
mRtcEngine = null;
}
}
```
```kotlin
private fun cleanupAgoraEngine() {
mRtcEngine?.apply {
stopPreview()
leaveChannel()
}
mRtcEngine = null
}
```
### 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 `` in package `com.example.` with your project's name.
### Complete sample code for real-time Video Calling [#complete-sample-code-for-real-time-video-calling]
Java
Kotlin
```java
package com.example.;
public class MainActivity extends AppCompatActivity {
private static final int PERMISSION_REQ_ID = 22;
private String myAppId = "";
private String channelName = "";
private String 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());
}
}
```
```kotlin
package com.example.
class MainActivity : AppCompatActivity() {
companion object {
private const val PERMISSION_REQ_ID = 22
}
private val myAppId = ""
private val channelName = ""
private val 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 {
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,
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()
}
}
}
```
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.
### 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.

### Sample code to create the user interface [#sample-code-to-create-the-user-interface]
```xml
```
## 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  **Sync Project with Gradle Files** to resolve project dependencies and update the configuration.
4. After synchronization is successful, click  **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]
### 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)
<_PlatformProcessedMarker close="true" />
<_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.

## Prerequisites [#prerequisites-1]
## Set up your project [#set-up-your-project-1]
The following figure illustrates the essential steps:
### Quick start sequence [#quick-start-sequence-1]

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 = "" // 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:
To ensure that you receive all Video SDK events, set the engine event handler before joining a channel.
```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. |
* 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).
### 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()
```
caution
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.
### 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:
### Complete sample code for real-time Video Calling [#complete-sample-code-for-real-time-video-calling-1]
```swift
class ViewController: UIViewController {
let appId = "" // Replace with your actual App ID
let channelName = "demo" // Replace with your actual channel name
let 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)
}
}
```
### 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
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)
}
}
```
## Implement Video Calling [#implement-video-calling-1]
The following figure illustrates the essential steps:
### Quick start sequence [#quick-start-sequence-2]

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 UIKit
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 = "" // 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:
To ensure that you receive all Video SDK events, set the engine event handler before joining a channel.
```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. |
* 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).
### 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()
```
caution
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.
### 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:
### Complete sample code for real-time Video Calling [#complete-sample-code-for-real-time-video-calling-2]
```swift
class ViewController: UIViewController {
let appId = "" // Replace with your actual App ID
let channelName = "demo" // Replace with your actual channel name
let 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)
}
}
```
### 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
// ViewController.swift
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]
The following figure illustrates the essential steps:
### Quick start sequence [#quick-start-sequence-3]

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-2]
Add the following import to your swift file:
```swift
import UIKit
import AgoraRtcKit
```
### Initialize the engine [#initialize-the-engine-3]
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-3]
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 = "" // 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-3]
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:
To ensure that you receive all Video SDK events, set the engine event handler before joining a channel.
```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-3]
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-3]
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-3]
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-3]
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. |
* 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).
### Start and close the app [#start-and-close-the-app-3]
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()
```
caution
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.
### Complete sample code [#complete-sample-code-3]
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:
### Complete sample code for real-time Video Calling [#complete-sample-code-for-real-time-video-calling-3]
```swift
class ViewController: UIViewController {
let appId = "" // Replace with your actual App ID
let channelName = "demo" // Replace with your actual channel name
let 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)
}
}
```
### Create a user interface [#create-a-user-interface-3]
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-3]
```swift
// ViewController.swift
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)
}
}
```
<_PlatformProcessedMarker close="true" />
<_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.

## Prerequisites [#prerequisites-2]
## Set up your project [#set-up-your-project-2]
The following figure illustrates the essential steps:
### Quick start sequence [#quick-start-sequence-4]

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-3]
Add the following import to your swift file:
```swift
import Cocoa
import AgoraRtcKit
```
### Initialize the engine [#initialize-the-engine-4]
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-4]
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 = "" // 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-4]
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:
To ensure that you receive all Video SDK events, set the engine event handler before joining a channel.
```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-4]
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-4]
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-4]
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-4]
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. |
* 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).
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-4]
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()
```
caution
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.
### Complete sample code [#complete-sample-code-4]
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:
### Complete sample code for Video Calling [#complete-sample-code-for-video-calling]
```swift
// ViewController.swift
class ViewController: NSViewController {
let appId = "" // Replace with your actual App ID
let channelName = "demo" // Replace with your actual channel name
let 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)
}
}
```
### Create a user interface [#create-a-user-interface-4]
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-4]
```swift
// 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)
}
}
```
## Implement Video Calling [#implement-video-calling-2]
The following figure illustrates the essential steps:
### Quick start sequence [#quick-start-sequence-5]

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-4]
Add the following import to your swift file:
```swift
import Cocoa
import AgoraRtcKit
```
### Initialize the engine [#initialize-the-engine-5]
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-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 `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 = "" // 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-5]
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:
To ensure that you receive all Video SDK events, set the engine event handler before joining a channel.
```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-5]
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-5]
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-5]
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-5]
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. |
* 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).
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-5]
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()
```
caution
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.
### Complete sample code [#complete-sample-code-5]
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:
### Complete sample code for Video Calling [#complete-sample-code-for-video-calling-1]
```swift
// ViewController.swift
class ViewController: NSViewController {
let appId = "" // Replace with your actual App ID
let channelName = "demo" // Replace with your actual channel name
let 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)
}
}
```
### Create a user interface [#create-a-user-interface-5]
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-5]
```swift
// 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]
The following figure illustrates the essential steps:
### Quick start sequence [#quick-start-sequence-6]

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-5]
Add the following import to your swift file:
```swift
import Cocoa
import AgoraRtcKit
```
### Initialize the engine [#initialize-the-engine-6]
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-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 `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 = "" // 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-6]
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:
To ensure that you receive all Video SDK events, set the engine event handler before joining a channel.
```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-6]
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-6]
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-6]
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-6]
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. |
* 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).
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-6]
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()
```
caution
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.
### Complete sample code [#complete-sample-code-6]
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:
### Complete sample code for Video Calling [#complete-sample-code-for-video-calling-2]
```swift
// ViewController.swift
class ViewController: NSViewController {
let appId = "" // Replace with your actual App ID
let channelName = "demo" // Replace with your actual channel name
let 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)
}
}
```
### Create a user interface [#create-a-user-interface-6]
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-6]
```swift
// 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)
}
}
```
<_PlatformProcessedMarker close="true" />
<_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.
## 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.

## Prerequisites [#prerequisites-3]
## Set up your project [#set-up-your-project-3]
Create a new project
Add to an existing project
To initialize a new Vite project, take the following steps:
1. Open a terminal and run the following command:
```bash
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:
```bash
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.
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
```
### Install the SDK [#install-the-sdk-1]
Add the Video SDK to your project:
```bash
npm install agora-rtc-sdk-ng
```
## Implement Video Calling [#implement-video-calling-3]
The following figure illustrates the essential steps:
### Quick start sequence [#quick-start-sequence-7]

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-7]
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("");
}
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`.
To ensure that you receive all Video SDK events, set up event listeners before joining a channel.
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-7]
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-7]
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-7]
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.
### Complete sample code for real-time Video Calling [#complete-sample-code-for-real-time-video-calling-4]
```javascript
// 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();
```
### Create a user interface [#create-a-user-interface-7]
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-7]
```html
Web SDK Video Quickstart
Web SDK Video Quickstart
JoinLeave
```
## 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:
```bash
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:

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.
Information
* 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`).
## Reference [#reference-3]
### Sample project [#sample-project-1]
* [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-1]
* [`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+) |
* 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.
### 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:

### 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
```
* 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
```
Visit the [Download](/en/api-reference/sdks?product=video\&platform=web) page to obtain the link for the latest SDK version.
### Frequently asked questions [#frequently-asked-questions-1]
**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):
```bash
export NODE_OPTIONS=--openssl-legacy-provider
```
* Temporarily switch to a lower version of Node.js.
<_PlatformProcessedMarker close="true" />
<_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.

## Prerequisites [#prerequisites-4]
## Set up your project [#set-up-your-project-4]
Create a new project
Add to an existing project
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**.
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.
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-2]
Install the Agora Video SDK:
1. Download the latest Windows [SDK](/en/api-reference/sdks?product=video\&platform=windows).
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]
The following figure illustrates the essential steps:
### Quick start sequence [#quick-start-sequence-8]

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-7]
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-8]
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-7]
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.
To ensure that you receive all Video SDK events, set the engine event handler before joining a channel.
```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-7]
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-8]
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-8]
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;
```
Caution
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).
### Complete sample code [#complete-sample-code-8]
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]
```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 ""
// Insert the temporary token obtained from the Agora Console
#define 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(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(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;
}
```
### Create a user interface [#create-a-user-interface-8]
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:

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]
### Sample project [#sample-project-2]
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-2]
* [`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-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)
<_PlatformProcessedMarker close="true" />
<_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.

## Prerequisites [#prerequisites-5]
## 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-3]
npm integration
Manual Integration
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.
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.
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
```
* 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.
Compile the latest SDK from the [Electron SDK repository](https://github.com/AgoraIO-Extensions/Electron-SDK/tree/main/) and integrate it into your application.
## Implement Video Calling [#implement-video-calling-5]
The following figure illustrates the essential steps:
### Quick start sequence [#quick-start-sequence-9]

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-7]
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-8]
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-8]
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-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 `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);
```
To ensure that you receive all Video SDK events, set the engine event handler before joining a channel.
### Display the local video [#display-the-local-video-9]
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-9]
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-7]
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();
```
caution
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.
\:::
### Complete sample code [#complete-sample-code-9]
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]
```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,
});
};
```
Information
* 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).
To test the complete code, see [Test the sample code](#test-the-sample-code).
### Create a user interface [#create-a-user-interface-9]
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-8]
```html
Electron Quickstart
Electron Quickstart
```
## 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]
### Sample project [#sample-project-3]
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-3]
* [`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-3]
* [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)
<_PlatformProcessedMarker close="true" />
<_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.

## Prerequisites [#prerequisites-6]
## Set up your project [#set-up-your-project-6]
Create a new project
Add to an existing project
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
```
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`.
### Install the SDK [#install-the-sdk-4]
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]
The following figure illustrates the essential steps:
### Quick start sequence [#quick-start-sequence-10]

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-9]
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 _initializeAgoraVoiceSDK() async {
_engine = createAgoraRtcEngine();
await _engine.initialize(const RtcEngineContext(
appId: "<-- Insert app Id -->",
channelProfile: ChannelProfileType.channelProfileCommunication,
));
}
```
### 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.
For Video Calling, set the `clientRoleType` to `clientRoleBroadcaster`.
```dart
// Join a channel
Future _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-8]
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.
To ensure that you receive all Video SDK events, register the event handler before joining a channel.
### Display the local video [#display-the-local-video-10]
To display the local video, enable the video module by calling `enableVideo`, then start the local video preview with `startPreview`.
```dart
Future _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-10]
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-8]
Request microphone and camera permissions for Video Calling.
```dart
Future _requestPermissions() async {
await [Permission.microphone, Permission.camera].request();
}
```
Information
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 |
### Start and close the app [#start-and-close-the-app-8]
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 _cleanupAgoraEngine() async {
await _engine.leaveChannel();
await _engine.release();
}
```
Warning
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.
### 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 the following code to replace the entire contents of the `.dart` file in your project.
### Complete sample code for Video Calling [#complete-sample-code-for-video-calling-3]
```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 {
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 _startVideoCalling() async {
await _requestPermissions();
await _initializeAgoraVideoSDK();
await _setupLocalVideo();
_setupEventHandlers();
await _joinChannel();
}
// Requests microphone and camera permissions
Future _requestPermissions() async {
await [Permission.microphone, Permission.camera].request();
}
// Set up the Agora RTC engine instance
Future _initializeAgoraVideoSDK() async {
_engine = createAgoraRtcEngine();
await _engine.initialize(const RtcEngineContext(
appId: appId,
channelProfile: ChannelProfileType.channelProfileCommunication,
));
}
// Enables and starts local video preview
Future _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 _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 _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,
);
}
}
}
```
Information
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.
### Create a user interface [#create-a-user-interface-10]
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-9]
```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:

## Reference [#reference-6]
### Sample project [#sample-project-4]
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-4]
* [`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-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)
<_PlatformProcessedMarker close="true" />
<_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.

## Prerequisites [#prerequisites-7]
## 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-5]
Download and add the Agora Video SDK to your React Native project. Choose either of the following methods:
npm
Yarn
In your project folder, execute the following command:
```bash
npm i --save react-native-agora
```
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
```
information
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
## Implement Video Calling [#implement-video-calling-7]
The following figure illustrates the essential steps:
### Quick start sequence [#quick-start-sequence-11]

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
createAgoraRtcEngine,
ChannelProfileType,
ClientRoleType,
IRtcEngine,
RtcSurfaceView,
RtcConnection,
IRtcEngineEventHandler,
} from 'react-native-agora';
```
### Initialize the engine [#initialize-the-engine-10]
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-11]
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,',
});
}
};
```
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.
### Subscribe to Video SDK events [#subscribe-to-video-sdk-events-9]
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);
};
```
To ensure that you receive all Video SDK events, register the event handler before joining a channel.
### Enable the video module [#enable-the-video-module-9]
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-11]
To display the local user's video stream, use the `RtcSurfaceView` component. Set the `uid` to `0` and `style` the video container.
```tsx
```
### Display remote video [#display-remote-video-11]
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
```
### Handle permissions [#handle-permissions-9]
To access the camera and microphone, ensure that the user grants the necessary permissions when the app starts.
Android
iOS
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,
]);
}
};
```
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. |
### 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();
};
};
```
After calling `release,` methods and callbacks of the SDK are no longer available. To use the SDK functions again, create a new engine instance.
### Complete sample code [#complete-sample-code-11]
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.
### Complete sample code for real-time Video Calling [#complete-sample-code-for-real-time-video-calling-5]
```tsx
// Import React Hooks
// Import user interface elements
SafeAreaView,
ScrollView,
StyleSheet,
Text,
View,
Switch,
} from 'react-native';
// Import components related to obtaining Android device permissions
// Import Agora SDK
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 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(); // 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 (
Agora Video SDK Quickstart
Join Channel
Leave Channel
Audience {
setIsHost(switchValue);
if (isJoined) {
leave();
}
}}
value={isHost}
/>
Host
{isJoined && isHost ? (
Local user uid: {localUid}
) : (
Join a channel
)}
{isJoined && remoteUid !== 0 ? (
Remote user uid: {remoteUid}
) : (
{isJoined && !isHost ? 'Waiting for remote user to join' : ''}
)}
{message}
);
// 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;
```
### Create a user interface [#create-a-user-interface-11]
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-10]
```tsx
// Import user interface elements
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 (
Agora Video SDK Quickstart
Join Channel
Leave Channel
Audience {
setIsHost(switchValue);
if (isJoined) {
leave();
}
}}
value={isHost}
/>
Host
{isJoined && isHost ? (
Local user uid: {localUid}
) : (
Join a channel
)}
{isJoined && remoteUid !== 0 ? (
Remote user uid: {remoteUid}
) : (
{isJoined && !isHost ? 'Waiting for remote user to join' : ''}
)}
{message}
);
};
// 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.
Caution
Some simulators may not support all the functions of this project. Best practice is to run the project on a physical device.
\:::
Android
iOS
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`.
To run on 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.
Information
For detailed steps on running the app on a real Android or iOS device, please refer to [Running On Device](https://reactnative.dev/docs/running-on-device).
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]
### Sample project [#sample-project-5]
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-5]
* [`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-5]
* [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 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.

React SDK and Web SDK relationship
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.
## Prerequisites [#prerequisites-8]
## 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-6]
Use one of the following methods to install the project dependencies:
npm
CDN
Navigate to the project folder and run the following command to install the Video SDK:
```bash
npm i agora-rtc-react
```
To integrate React JS dependencies and the Agora SDK using CDN, add the following code to your HTML file:
```html
```
## Implement Video Calling [#implement-video-calling-8]
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:
```tsx
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";
```
### Initialize the client [#initialize-the-client]
Use the `createClient` method provided by the SDK to create a client object for the `component. Wrap the` component with \`\` to enable state management and access operation-related hooks.
For Video Calling, set the `mode` property of `ClientConfig` to `"rtc"`.
```tsx
export const VideoCalling = () => {
const client = AgoraRTC.createClient({ mode: "rtc", codec: "vp8" });
return(
);
}
```
### Join a channel [#join-a-channel-12]
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`:
```tsx
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`:
```tsx
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`:
```tsx
const [micOn, setMic] = useState(true);
const [cameraOn, setCamera] = useState(true);
const { localMicrophoneTrack } = useLocalMicrophoneTrack(micOn);
const { localCameraTrack } = useLocalCameraTrack(cameraOn);
usePublish([localMicrophoneTrack, localCameraTrack]);
```
### Display remote video [#display-remote-video-12]
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:
```tsx
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:
```tsx
{remoteUsers.map((user) => (
{user.uid}
))}
```
### 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-12]
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.
### Complete sample code for real-time Video Calling [#complete-sample-code-for-real-time-video-calling-6]
```tsx
LocalUser,
RemoteUser,
useIsConnected,
useJoin,
useLocalMicrophoneTrack,
useLocalCameraTrack,
usePublish,
useRemoteUsers,
} from "agora-rtc-react";
export const VideoCalling = () => {
const client = AgoraRTC.createClient({ mode: "rtc", codec: "vp8" });
return(
);
}
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 (
<>