This guide shows how to integrate the ARTC SDK into your iOS project to build a real-time audio and video application, suitable for use cases such as interactive live streaming and video calls.
Feature description
Before you begin, understand the following key concepts:
ARTC SDK: An SDK provided by Alibaba Cloud that helps developers quickly implement real-time audio and video interaction.
Global Realtime Transport Network (GRTN): A globally distributed network engineered for real-time media, ensuring ultra-low latency, high-quality, and secure communication.
Channel: A virtual room that users join to communicate with each other. All users in the same channel can interact in real time.
Host: A user who can publish audio and video streams in a channel and subscribe to streams published by other hosts.
Viewer: A user who can subscribe to audio and video streams in a channel but cannot publish their own.
Basic process for implementing real-time audio and video interaction:
Call
setChannelProfileto set the scenario, and calljoinChannelto join a channel:Video call scenario: All users are hosts and can both publish and subscribe to streams.
Interactive streaming scenario: Roles must be set using
setClientRolebefore joining a channel. For users who will publish streams, set the role to host. If a user only needs to subscribe to streams, set the role to viewer.
After joining the channel, users have different publishing and subscribing behaviors based on their roles:
All users can receive audio and video streams within that channel.
A host can publish audio and video streams in the channel.
If a viewer wants to publish streams, call the
setClientRolemethod to switch the role to host.
Sample project
ARTC SDK provides an open-source sample project for real-time audio and video apps.
Environment requirements
Before running the demo project, ensure your development environment meets the following requirements:
Development tool: Xcode 14.0 or later. The latest official version is recommended.
Recommended configuration: CocoaPods 1.9.3 or later.
Test device: A device running iOS 9.0 or later.
We recommend using physical devices for testing, as emulators may not support all features.
Network: A stable Internet connection.
Application setup: Obtain the AppID and AppKey of the ARTC application.
Create a project (optional)
This section describes how to create a new project and add the necessary permissions for audio and video interaction. You can 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.

Configure your project settings, including
Bundle Identifier,Signing, andMinimum Deployments.
Configure your project
Step 1: Import SDK
Automatic integration through CocoaPods (recommended)
Open Terminal and install CocoaPods. If you have already installed it, skip this step.
sudo gem install cocoapodsOpen Terminal, navigate to the root directory of your project, and run the following command to create a Podfile.
pod initOpen and edit the generated Podfile to add the RTC SDK dependency. The latest available version is 7.9.1.
target 'MyApp' do
use_frameworks!
# Replace ${latest version} with a specific version number.
pod 'AliVCSDK_ARTC', '~> ${latest version}'
endIn Terminal, run the following command to update the CocoaPods dependency library in your project.
pod installAfter successful execution, a project file with the .xcworkspace extension is generated in the project folder. Double-click this file to open the project in Xcode to automatically load the CocoaPods dependencies in the workspace.

Manual integration
Download the SDK, obtain the latest ARTC SDK package, and decompress it.
Copy the framework files from the decompressed package to your project directory.
Open your project in Xcode, select
File -> Add Files to "xxx"from the menu, and add the SDK library files to your project.
Select the target and set the imported framework files to
"Embed & Sign".

Step 2: Configure permissions
You must add recording and camera permissions
Add camera and microphone permissions Privacy - Camera Usage Description and Privacy - Microphone Usage Description to the Info.plist file.

Enable audio background mode (optional).
As shown in the figure, check Audio, AirPlay, and Picture in Picture.

Step 3: Create a user interface
Create a user interface that fits your real-time interaction scenario. For this example of 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
This section explains how to use the ARTC SDK to build a basic real-time audio and video application. You can copy the complete code sample into your project to test the functionality. The steps below explain the core API calls.
The following diagram shows the basic workflow for implementing a video call:
Code example for a video call scenario
For details on the complete sample code, see Run ARTC demo project for iOS.
1. Request permissions
Although the SDK checks for necessary permissions when you start a call, we recommend checking for camera and microphone permissions before initiating a call to ensure a smooth user experience.
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)
}
}
// Usage example
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.")
}
}
2. Get an authentication token
Joining an ARTC channel requires an authentication token to verify the user's identity. For details on how the token is generated, see Implement token-based authentication. A token can be generated using a single-parameter method or a multi-parameter method. The method you use determines which joinChannel API you need to call.
For production environments:
Because generating a token requires your AppKey, hardcoding it on the client side poses a security risk. In a production environment, we strongly recommend generating the token on your server and sending it to the client.
For development and debugging:
During development, if your business server does not yet have the logic to generate tokens, you can temporarily use the token generation logic from the APIExample to create a temporary token. The reference code is as follows:
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.
* String signing (SHA256)
*/
private static func GetSHA256(_ input: String) -> String {
// Convert 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()
}
}
3. Import the ARTC SDK module
// Import the ARTC module.
import AliVCSDK_ARTC4. Create and initialize the engine
Create the RTC engine
Call the sharedInstance method to create an
AliRtcEngineinstance.private var rtcEngine: AliRtcEngine? = nil // Create the engine and set the delegate. let engine = AliRtcEngine.sharedInstance(self, extras:nil) ... self.rtcEngine = engineInitialize the engine
Call
setChannelProfileto set the channel toAliRTCInteractiveLive(interactive mode).Depending on your business needs, you can choose interactive mode, which is suitable for interactive entertainment scenarios, or communication mode, which is suitable for one-to-one or one-to-many broadcasting. Choosing the right mode ensures a smooth user experience and efficient use of network resources.
Mode
Publishing
Subscribing
Description
Interactive mode
Limited by role. Only users with the host role can publish streams.
Participants can flexibly switch roles throughout the session.
No role restrictions. All participants have permission to subscribe to streams.
In interactive mode, events such as a host joining or leaving the channel, or starting to publish a stream, are notified to viewers in real time. Conversely, the viewers' activities are not notified to the hosts, ensuring uninterrupted streaming.
In this mode, hosts are responsible for interaction, while viewers only consumes content. If your business needs might change, consider using interactive mode by default. Its flexibility allows you to adapt to different interaction requirements by adjusting user roles.
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, participants are aware of each other's presence.
Although this mode does not differentiate user roles, it is functionally equivalent to the host role in interactive mode. The goal is to simplify operations, allowing users to achieve the desired functionality with fewer API calls.
Call
setClientRoleto set the user role toAliRTCSdkInteractive(host) orAliRTCSdkLive(viewer). The host role publishes and subscribes to streams by default, while the viewer role only subscribes and has local preview and publishing disabled by default.Note: When a user switches from host to viewer, the system stops publishing the local audio and video streams, but existing subscriptions are not affected. When a user switches from viewer to host, the system starts publishing the local audio and video streams, and existing subscriptions are not affected.
// Set the channel profile to Interactive Mode. Use AliRtcInteractivelive for all RTC scenarios. engine.setChannelProfile(AliRtcChannelProfile.interactivelive) // Set the client role. Use AliRtcClientRoleInteractive for users who need to both publish and subscribe. Use AliRtcClientRolelive for users who only subscribe. engine.setClientRole(AliRtcClientRole.roleInteractive)
Implement common callbacks
If the SDK encounters an issue during operation, it will first attempt to recover automatically using its internal retry mechanisms. For errors that it cannot resolve on its own, the SDK will notify your application through predefined callback interfaces.
The following are key callbacks for issues the SDK cannot handle, which your application must listen for and respond to:
Cause of exception
Callback and parameters
Solution
Description
Authentication failed
result in onJoinChannelResult returns AliRtcErrJoinBadToken.
The app should check if the token is correct.
When a user calls an API, if authentication fails, the API's callback will return an authentication failure error.
Token about to expire
onAuthInfoWillExpire
Retrieve a new token and call refreshAuthInfo to update the information.
A token expiration error can occur either when an API is called or during runtime. The error is reported through API callbacks or a separate error callback.
Token expired
onAuthInfoExpired
The app must rejoin the channel.
A token expiration error can occur either when an API is called or during runtime. The error is reported through API callbacks or a separate error callback.
Network connection issue
onConnectionStatusChange callback returns AliRtcConnectionStatusFailed.
The app must rejoin the channel.
The SDK can automatically recover from brief network disconnections. If the disconnection time exceeds a threshold, it will time out. The app must check the network status and guide the user to rejoin.
Kicked from channel
onBye
AliRtcOnByeUserReplaced: Check if another user has joined with the same userId.
AliRtcOnByeBeKickedOut: The user was kicked out of the channel and needs to rejoin.
AliRtcOnByeChannelTerminated: The channel was terminated, and the user needs to rejoin.
The RTC service allows an administrator to remove participants.
Local device exception
onLocalDeviceException
Check app permissions and whether 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: Must handle. The token is about to expire. Your app needs to get a new token for the current channel and user, then call refreshAuthInfo. */ } func onAuthInfoExpired() { "onAuthInfoExpired".printLog() /* TODO: Must handle. 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: Must handle. This callback is triggered if another device logs in with the same UserID, kicking the current device out of the channel. */ } func onLocalDeviceException(_ deviceType: AliRtcLocalDeviceType, exceptionType: AliRtcLocalDeviceExceptionType, message msg: String?) { "onLocalDeviceException deviceType: \(deviceType) exceptionType: \(exceptionType)".printLog() /* TODO: Must handle. We recommend notifying the user of a device error. This callback is triggered when the SDK's internal recovery strategies have failed.*/ } func onConnectionStatusChange(_ status: AliRtcConnectionStatus, reason: AliRtcConnectionStatusChangeReason) { "onConnectionStatusChange status: \(status) reason: \(reason)".printLog() if status == .failed { /* TODO: Must handle. We recommend notifying the user. This callback is triggered when the SDK's internal recovery strategies have failed. */ } else { /* TODO: Optional. You can add business logic here, typically for analytics or UI updates. */ } } }
5. Set audio and video properties
Set audio properties
Call
setAudioProfileto set the audio encoding mode and scenario.// Set the audio profile. The default is high-quality mode (AliRtcEngineHighQualityMode) and music scenario (AliRtcSceneMusicMode). engine.setAudioProfile(AliRtcAudioProfile.engineHighQualityMode, audio_scene: AliRtcAudioScenario.sceneMusicMode)Set video properties
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)
6.Set publishing and subscribing properties
Configure the publishing of audio/video streams and set the default to subscribe to all users' streams:
Call
publishLocalAudioStreamto publish a audio stream.Call
publishLocalVideoStreamto publish a video stream. For an audio-only call, you can set this to false.
// By default, the SDK publishes the audio stream. Calling publishLocalAudioStream(true) is optional.
engine.publishLocalVideoStream(true)
// By default, the SDK publishes the video stream. For a video call, calling publishLocalVideoStream(true) is optional.
// To stop publishing video for a voice-only call, call publishLocalVideoStream(false).
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 audio and video streams and subscribes to the audio and video streams of all other users in the channel. You can call the methods above to override this default behavior.
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()8. Join a channel
Call joinChannel to join the channel. If the token was generated using the single-parameter method, call the joinChannel[3/3] operation. The result is returned in the onJoinChannelResult callback. A result of 0 indicates a successful join. A non-zero result may indicate an invalid token.
let ret = self.rtcEngine?.joinChannel(joinToken, channelId: nil, userId: nil, name: nil) { [weak self] errCode, channelId, userId, elapsed in
if errCode == 0 {
// success
}
else {
// failed
}
let resultMsg = "\(msg) \n CallbackErrorCode: \(errCode)"
resultMsg.printLog()
UIAlertController.showAlertWithMainThread(msg: resultMsg, vc: self!)
}
let resultMsg = "\(msg) \n ReturnErrorCode: \(ret ?? 0)"
resultMsg.printLog()
if ret != 0 {
UIAlertController.showAlertWithMainThread(msg: resultMsg, vc: self)
}After joining the channel, the SDK will publish and subscribe to streams according to the parameters set before joining.
The SDK automatically publishes and subscribes by default to reduce the number of API calls the client needs to make.
9. Set the remote view
When a remote user starts or stops publishing a stream, the onRemoteTrackAvailableNotify callback is triggered. In this callback, you can set up or remove the remote user's view. Sample code:
func onRemoteTrackAvailableNotify(_ uid: String, audioTrack: AliRtcAudioTrack, videoTrack: AliRtcVideoTrack) {
"onRemoteTrackAvailableNotify uid: \(uid) audioTrack: \(audioTrack) videoTrack: \(videoTrack)".printLog()
// 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)
}
}10. Leave the channel and destroy the engine
When the session ends, leave the channel and destroy the engine to release resources:
Call
stopPreviewto stop the video preview.Call
leaveChannelto leave the channel.Call
destroyto destroy the engine and release related resources.
self.rtcEngine?.stopPreview()
self.rtcEngine?.leaveChannel()
AliRtcEngine.destroy()
self.rtcEngine = nil11. Effect demonstration

