Audio and video call on iOS
This guide shows how to integrate the ARTC SDK into your iOS project to build a simple real-time audio and video app for interactive live streaming and video calls.
Key concepts
Before you begin, it helps to understand the following key concepts:
-
ARTC SDK: Alibaba Cloud's SDK for quickly implementing real-time audio and video interactions.
-
GRTN: Alibaba Cloud's Global Realtime Transport Network, providing ultra-low latency, high-quality, secure, and reliable audio and video communication services.
-
channel: A virtual room for real-time audio and video interactions.
-
host: A role that allows a user to publish audio and video streams in a channel and subscribe to streams published by other hosts.
-
viewer: A role that enables a user to subscribe to audio and video streams in a channel but not publish streams.
-
Call
setChannelProfileto set the channel scenario, and then call joinChannel to join a channel:-
In a video call scenario, all users have the host role and can publish and subscribe to streams.
-
In an interactive streaming scenario, you must call
setClientRoleto set the user role. Set the role to host for users who will publish a stream. If a user only needs to subscribe to a stream, set their role to viewer.
-
-
After joining a channel, a user's role determines whether they can publish or subscribe to streams:
-
All users in a channel can subscribe to its audio and video streams.
-
A host can publish audio and video streams in the channel.
-
If a viewer needs to publish a stream, they must call the
setClientRolemethod to switch their role to host.
-
Sample project
The Alibaba Cloud ARTC SDK includes an open-source sample project for real-time audio and video interaction. You can download it or view the sample code.
Prerequisites
Before you run the sample project, make sure that your development environment meets the following requirements:
Development tool: Xcode 14.0 or later. Use the latest official version.
Recommended configuration: CocoaPods 1.9.3 or later.
Test device: A device that runs iOS 9.0 or later.
Use a physical device for testing. Some features may not be available on simulators.
Network environment: A stable network connection.
Application preparation: You will need the AppID and AppKey for your ApsaraVideo Real-time Communication application. For more information, see Create an application.
Create a project (optional)
This section guides you through creating a project and adding the required permissions for audio and video interaction. Skip this section if you already have a project.
-
Open Xcode, go to File > New > Project, and select the App template. On the next screen, set Interface to Storyboard and Language to Swift.
-
Modify the project settings as needed, including
Bundle Identifier,Signing, andMinimum Deployments.
Configure your project
Step 1: Import the SDK
CocoaPods
-
Open a terminal and install CocoaPods on your development machine. If CocoaPods is already installed, skip this step.
sudo gem install cocoapods
-
Open a terminal, navigate to your project root directory, and run the following command to create a Podfile.
pod init
-
Open the generated Podfile and add the ARTC SDK dependency. Replace ${latest version} in the code below with the specific version you want to install.
target 'MyApp' do
use_frameworks!
# Replace ${latest version} with a specific version number.
pod 'AliVCSDK_ARTC', '~> ${latest version}'
end
-
In the terminal, run the following command to install the CocoaPods dependencies for your project.
pod install
-
When the command finishes, an
.xcworkspacefile is created in your project folder. From now on, always open your project using this file to ensure the dependencies are loaded.
Manual
-
Download the latest ARTC SDK package from SDK Download and unzip it.
-
Copy the framework files from the unzipped SDK package to your project directory.
-
Open your project in Xcode. Choose
File -> Add Files to "xxx"to add the SDK library files to your project.
-
Select your target and set the embed option for the imported frameworks to
"Embed & Sign".

Step 2: Set permissions
-
Add the required permissions for microphone and camera access.
In your Info.plist file, add the permission keys for camera and microphone access: Privacy - Camera Usage Description and Privacy - Microphone Usage Description.

-
Enable background audio mode (optional).
As shown, select Audio, AirPlay, and Picture in Picture.

Step 3: Create a user interface
Create a user interface for your real-time interaction scenario. For example, in a multi-person video call, create a ScrollView. When a user joins the call, add a video view to this container. When a user leaves, remove their video view and refresh the layout.
Implementation steps
This section explains how to use the ARTC SDK to build a basic real-time audio and video application. You can copy the code sample to test the features quickly, then follow the steps to understand the core API calls.
The following diagram shows the basic workflow for a real-time audio and video call:
The following code sample implements a basic video call:
For details on the complete sample code and how to run it, see Run the ARTC demo for iOS.
Step 1: Request permissions
The SDK checks for app permissions when a call starts, but for a smooth user experience, check for camera and microphone permissions before initiating the call.
func checkMicrophonePermission(completion: @escaping (Bool) -> Void) {
let status = AVCaptureDevice.authorizationStatus(for: .audio)
switch status {
case .notDetermined:
AVCaptureDevice.requestAccess(for: .audio) { granted in
completion(granted)
}
case .authorized:
completion(true)
default:
completion(false)
}
}
func checkCameraPermission(completion: @escaping (Bool) -> Void) {
let status = AVCaptureDevice.authorizationStatus(for: .video)
switch status {
case .notDetermined:
AVCaptureDevice.requestAccess(for: .video) { granted in
completion(granted)
}
case .authorized:
completion(true)
default:
completion(false)
}
}
// Example usage
checkMicrophonePermission { granted in
if granted {
print("Microphone access granted.")
} else {
print("Microphone access denied.")
}
}
checkCameraPermission { granted in
if granted {
print("Camera access granted.")
} else {
print("Camera access denied.")
}
}
Step 2: Get an authentication token
Joining an ARTC channel requires an authentication token to verify the user's identity. For the token generation rules, see Token Authentication. A token can be generated by using a single-parameter or multi-parameter method, and you must call a different joinChannel API method in the SDK based on the method you use.
In a production environment:
Generating a token requires your AppKey. Hardcoding the AppKey on the client is a security risk. We strongly recommend generating the token on your app server and then sending it to the client.
During development and debugging:
If your app server lacks token generation logic, you can temporarily generate a token on the client.
class ARTCTokenHelper: NSObject {
/**
* RTC AppId
*/
public static let AppId = "<RTC AppId>"
/**
* RTC AppKey
*/
public static let AppKey = "<RTC AppKey>"
/**
* Generate a multi-parameter token for joining a channel based on channelId, userId, and timestamp.
*/
public func generateAuthInfoToken(appId: String = ARTCTokenHelper.AppId, appKey: String = ARTCTokenHelper.AppKey, channelId: String, userId: String, timestamp: Int64) -> String {
let stringBuilder = appId + appKey + channelId + userId + "\(timestamp)"
let token = ARTCTokenHelper.GetSHA256(stringBuilder)
return token
}
/**
* Generate a single-parameter token for joining a channel based on channelId, userId, and nonce.
*/
public func generateJoinToken(appId: String = ARTCTokenHelper.AppId, appKey: String = ARTCTokenHelper.AppKey, channelId: String, userId: String, timestamp: Int64, nonce: String = "") -> String {
let token = self.generateAuthInfoToken(appId: appId, appKey: appKey, channelId: channelId, userId: userId, timestamp: timestamp)
let tokenJson: [String: Any] = [
"appid": appId,
"channelid": channelId,
"userid": userId,
"nonce": nonce,
"timestamp": timestamp,
"token": token
]
if let jsonData = try? JSONSerialization.data(withJSONObject: tokenJson, options: []),
let base64Token = jsonData.base64EncodedString() as String? {
return base64Token
}
return ""
}
/**
* Sign a string using SHA256.
*/
private static func GetSHA256(_ input: String) -> String {
// Convert the input string to data.
let data = Data(input.utf8)
// Create a buffer to store the hash result.
var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
// Calculate the SHA-256 hash.
data.withUnsafeBytes {
_ = CC_SHA256($0.baseAddress, CC_LONG(data.count), &hash)
}
// Convert the hash to a hexadecimal string.
return hash.map { String(format: "%02hhx", $0) }.joined()
}
}
Step 3: Import the ARTC SDK
// Import the ARTC module.
import AliVCSDK_ARTC
Step 4: Create and initialize the engine
-
Create the RTC engine
Call the
sharedInstancemethod to create anAliRtcEngineinstance.private var rtcEngine: AliRtcEngine? = nil // Create the engine and set the delegate. let engine = AliRtcEngine.sharedInstance(self, extras:nil) ... self.rtcEngine = engine -
Initialize the engine
-
Call
setChannelProfileto set the channel profile toAliRTCInteractiveLive(interactive mode).Choose between interactive mode for entertainment scenarios and communication mode for one-to-one or group calls. Selecting the appropriate mode ensures optimal performance and network efficiency.
Mode
Publishing
Subscribing
Description
interactive mode
-
Limited by role. Only users with the host role can publish streams.
-
Participants can switch roles at any time during the session.
No role restrictions. All participants have permission to subscribe to streams.
-
In interactive mode, when a host joins, leaves, or starts publishing a stream, viewers are notified in real time. However, viewer activities are not sent to the host, which prevents interruptions to the stream.
-
The host drives the interaction, while the viewer consumes content. If viewers might need to interact, use this mode by default. Its flexibility allows you to change user roles as needed.
communication mode
No role restrictions. All participants have permission to publish streams.
No role restrictions. All participants have permission to subscribe to streams.
-
In communication mode, all participants are aware of each other's presence in the channel.
-
This mode does not use roles and is functionally equivalent to all users being a host in interactive mode. This simplifies API calls.
-
-
Call
setClientRoleto set the user role toAliRTCSdkInteractive(host) orAliRTCSdkLive(viewer). By default, a host both publishes and subscribes to streams, while a viewer only subscribes. For viewers, local preview and publishing are disabled by default.Note: When a user becomes a viewer, the SDK stops local stream publishing but maintains subscriptions. When a user becomes a host, the SDK starts local stream publishing, and subscriptions are unaffected.
// Set the channel profile to interactive mode. In RTC scenarios, always use AliRtcChannelProfile.interactivelive. engine.setChannelProfile(AliRtcChannelProfile.interactivelive) // Set the user role. Use AliRtcClientRole.roleInteractive for users who need to both publish and subscribe. Use AliRtcClientRole.live for users who only subscribe. engine.setClientRole(AliRtcClientRole.roleInteractive)
-
-
Implement common callbacks
The SDK attempts to recover from issues automatically. For unrecoverable errors, it notifies your app via callbacks.
Your application must handle the following key callbacks for unrecoverable SDK issues:
Cause
Callback and parameters
Solution
Description
Authentication failed
The
resultparameter in theonJoinChannelResultcallback returnsAliRtcErrJoinBadToken.Your app must verify the token.
If authentication fails when a user calls an API, the API's callback returns an authentication failure error.
Token about to expire
onAuthInfoWillExpire
When this callback is triggered, get a new token and call
refreshAuthInfoto update the authentication information.This error can occur during an API call or at runtime and is reported through an API or error callback.
Token expired
onAuthInfoExpired
When this callback is triggered, the user must rejoin the channel.
This error can occur during an API call or at runtime and is reported through an API or error callback.
Network connection issue
The onConnectionStatusChange callback returns AliRtcConnectionStatusFailed.
The user must rejoin the channel.
The SDK automatically recovers from brief disconnections. If a disconnection exceeds the timeout threshold, your app should check the network and prompt the user to rejoin the channel.
Kicked from channel
onBye
-
AliRtcOnByeUserReplaced: Check if another device has joined with the sameuserId. -
AliRtcOnByeBeKickedOut: The user was kicked by an administrator and must rejoin. -
AliRtcOnByeChannelTerminated: The channel was terminated, and the user must rejoin.
The RTC service allows an administrator to remove participants from a channel.
Local device exception
onLocalDeviceException
Check app permissions and ensure the hardware is working correctly.
When a local device exception occurs that the SDK cannot resolve, it notifies the app via a callback. The app should then intervene to check the device status.
extension VideoCallMainVC: AliRtcEngineDelegate { func onJoinChannelResult(_ result: Int32, channel: String, elapsed: Int32) { "onJoinChannelResult1 result: \(result)".printLog() } func onJoinChannelResult(_ result: Int32, channel: String, userId: String, elapsed: Int32) { "onJoinChannelResult2 result: \(result)".printLog() } func onRemoteUser(onLineNotify uid: String, elapsed: Int32) { // A remote user comes online. "onRemoteUserOlineNotify uid: \(uid)".printLog() } func onRemoteUserOffLineNotify(_ uid: String, offlineReason reason: AliRtcUserOfflineReason) { // A remote user goes offline. "onRemoteUserOffLineNotify uid: \(uid) reason: \(reason)".printLog() } func onRemoteTrackAvailableNotify(_ uid: String, audioTrack: AliRtcAudioTrack, videoTrack: AliRtcVideoTrack) { "onRemoteTrackAvailableNotify uid: \(uid) audioTrack: \(audioTrack) videoTrack: \(videoTrack)".printLog() } func onAuthInfoWillExpire() { "onAuthInfoWillExpire".printLog() /* TODO: Handle this callback. The token is about to expire. Your app must fetch a new token for the current channel and user, then call refreshAuthInfo. */ } func onAuthInfoExpired() { "onAuthInfoExpired".printLog() /* TODO: This must be handled. Notify the user that the token has expired, then leave the channel and destroy the engine. */ } func onBye(_ code: Int32) { "onBye code: \(code)".printLog() /* TODO: Handle this callback. It is triggered if another device logs in with the same UserID, which kicks the current device out of the channel. */ } func onLocalDeviceException(_ deviceType: AliRtcLocalDeviceType, exceptionType: AliRtcLocalDeviceExceptionType, message msg: String?) { "onLocalDeviceException deviceType: \(deviceType) exceptionType: \(exceptionType)".printLog() /* TODO: Handle this callback. Notify the user of a device error. This callback is triggered only after the SDK's internal recovery strategies fail. */ } func onConnectionStatusChange(_ status: AliRtcConnectionStatus, reason: AliRtcConnectionStatusChangeReason) { "onConnectionStatusChange status: \(status) reason: \(reason)".printLog() if status == .failed { /* TODO: Handle this callback. Notify the user of the connection failure. This callback is triggered only after the SDK's internal recovery strategies fail. */ } else { /* TODO: Optional. Add business logic here, typically for data analytics or UI updates. */ } } } -
Step 5: Set audio and video properties
-
Set audio properties
Call
setAudioProfileto set the audio encoding mode and audio scenario.// Set the audio profile. The default is high-quality mode (AliRtcAudioProfile.engineHighQualityMode) and music scenario (AliRtcAudioScenario.sceneMusicMode). engine.setAudioProfile(AliRtcAudioProfile.engineHighQualityMode, audio_scene: AliRtcAudioScenario.sceneMusicMode) -
Set video properties
You can set properties for the published video stream, such as resolution, bitrate, and frame rate.
// Set the video encoder configuration. let config = AliRtcVideoEncoderConfiguration() config.dimensions = CGSize(width: 720, height: 1280) config.frameRate = 20 config.bitrate = 1200 config.keyFrameInterval = 2000 config.orientationMode = AliRtcVideoEncoderOrientationMode.adaptive engine.setVideoEncoderConfiguration(config) engine.setCapturePipelineScaleMode(.post)
Step 6: Set publishing and subscribing properties
Configure local stream publishing and default remote stream subscriptions:
-
Call
publishLocalAudioStreamto publish the local audio stream. -
Call
publishLocalVideoStreamto publish the local video stream. For an audio-only call, set this tofalse.
// The SDK publishes the video stream by default, so calling publishLocalVideoStream(true) is optional.
engine.publishLocalVideoStream(true)
// The SDK publishes the audio stream by default. For a video call, calling publishLocalAudioStream(true) is optional.
// For an audio-only call, you must call publishLocalVideoStream(false) to disable video publishing.
engine.publishLocalAudioStream(true)
// Set the default to subscribe to all remote audio and video streams.
engine.setDefaultSubscribeAllRemoteAudioStreams(true)
engine.subscribeAllRemoteAudioStreams(true)
engine.setDefaultSubscribeAllRemoteVideoStreams(true)
engine.subscribeAllRemoteVideoStreams(true)
By default, the SDK automatically publishes local streams and subscribes to remote streams. You can call the methods above to disable this automatic behavior.
Step 7: Start local preview
-
Call
setLocalViewConfigto set up the local render view and configure local video display properties. -
Call the
startPreviewmethod to start the local video preview.
let videoView = self.createVideoView(uid: self.userId)
let canvas = AliVideoCanvas()
canvas.view = videoView.canvasView
canvas.renderMode = .auto
canvas.mirrorMode = .onlyFrontCameraPreviewEnabled
canvas.rotationMode = ._0
self.rtcEngine?.setLocalViewConfig(canvas, for: AliRtcVideoTrack.camera)
self.rtcEngine?.startPreview()
Step 8: Join a channel
Call joinChannel to join a channel. We recommend that you use the single-parameter method by calling the joinChannel[3/3] interface. After the call, you must check the return value and also get the result from the onJoinChannelResult callback. If the return value is 0 and the result is 0, you have successfully joined the channel. Otherwise, check if the provided token is invalid.
let param = AliRtcChannelParam()
let ret = self.rtcEngine?.joinChannel(joinToken, channelParam: param) { [weak self] errCode, channelId, userId, elapsed in
if errCode == 0 {
// success
}
else {
// failed
}
let resultMsg = "Join channel callback. ErrorCode: \(errCode)"
resultMsg.printLog()
UIAlertController.showAlertWithMainThread(msg: resultMsg, vc: self!)
}
let resultMsg = "Join channel API call. Return code: \(ret ?? 0)"
resultMsg.printLog()
if ret != 0 {
UIAlertController.showAlertWithMainThread(msg: resultMsg, vc: self)
}
-
After a user joins a channel, the SDK publishes and subscribes to streams according to the parameters set before joining.
-
The SDK automatically publishes and subscribes by default to reduce the number of API calls your client needs to make.
Step 9: Set the remote view
When a remote user starts or stops publishing a stream, the SDK triggers the onRemoteTrackAvailableNotify callback. In this callback, you can set up or remove the remote user's view. The following code shows an example:
func onRemoteTrackAvailableNotify(_ uid: String, audioTrack: AliRtcAudioTrack, videoTrack: AliRtcVideoTrack) {
"onRemoteTrackAvailableNotify uid: \(uid) audioTrack: \(audioTrack) videoTrack: \(videoTrack)".printLog()
// The stream status of a remote user.
if audioTrack != .no {
let videoView = self.videoViewList.first { $0.uidLabel.text == uid }
if videoView == nil {
_ = self.createVideoView(uid: uid)
}
}
if videoTrack != .no {
var videoView = self.videoViewList.first { $0.uidLabel.text == uid }
if videoView == nil {
videoView = self.createVideoView(uid: uid)
}
let canvas = AliVideoCanvas()
canvas.view = videoView!.canvasView
canvas.renderMode = .auto
canvas.mirrorMode = .onlyFrontCameraPreviewEnabled
canvas.rotationMode = ._0
self.rtcEngine?.setRemoteViewConfig(canvas, uid: uid, for: AliRtcVideoTrack.camera)
}
else {
self.rtcEngine?.setRemoteViewConfig(nil, uid: uid, for: AliRtcVideoTrack.camera)
}
if audioTrack == .no && videoTrack == .no {
self.removeVideoView(uid: uid)
self.rtcEngine?.setRemoteViewConfig(nil, uid: uid, for: AliRtcVideoTrack.camera)
}
}
Step 10: Leave channel and destroy engine
When the session ends, leave the channel and destroy the engine to release all resources. Follow these steps:
-
Call
stopPreviewto stop the local video preview. -
Call
leaveChannelto leave the channel. -
Call
destroyto destroy the engine instance and release its resources.
self.rtcEngine?.stopPreview()
self.rtcEngine?.leaveChannel()
AliRtcEngine.destroy()
self.rtcEngine = nil
Step 11: Result

