All Products
Search
Document Center

ApsaraVideo Live:Audio and video call on iOS

Last Updated:Nov 03, 2026

Integrate the ARTC SDK into your iOS project to build a real-time audio and video app for interactive live streaming and video calls.

Key concepts

Before you begin, review the following key concepts:

  • ARTC SDK: Alibaba Cloud's SDK for 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.

image
  1. Call setChannelProfile to 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 setClientRole to 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.

  2. 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 setClientRole method to switch their role to host.

Sample project

The ARTC SDK provides an open-source sample project for real-time audio and video interaction. Download it or view the sample code.

Prerequisites

Before running the sample project, ensure your development environment meets the following requirements:

  • Development tool: Xcode 14.0 or later. Use the latest stable version if possible.

  • Recommended setup: CocoaPods 1.9.3 or later.

  • Test device: A physical iOS device running iOS 9.0 or later.

Note

Use a physical device for testing. Simulators may lack certain features.

  • Network: A stable network connection is required.

  • Application setup: Obtain the AppID and AppKey for your ApsaraVideo Real-time Communication application. For more information, see Create Application.

Create a project (optional)

Create a project and add the required permissions for audio and video interaction. Skip this section if you already have a project.

  1. Open Xcode, go to File > New > Project, and select the App template. On the next screen, set Interface to Storyboard and Language to Swift.

  2. Modify the project settings as needed, including Bundle Identifier, Signing, and Minimum Deployments.

Configure your project

Step 1: Import the SDK

Maven automatic integration (recommended)

  1. Open settings.gradle in the project root. Add the ARTC SDK Maven repository under dependencyResolutionManagement/repositories:

    dependencyResolutionManagement {
        repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
        repositories {
            google()
            mavenCentral()
            // Add the Maven repository for the ARTC SDK.
            maven { url 'https://maven.aliyun.com/repository/google' }
            maven { url 'https://maven.aliyun.com/repository/public' }
        }
    }
    Note

    If your Android Gradle Plugin (AGP) version is lower than 7.1.0, the settings.gradle file may not contain the dependencyResolutionManagement block, as documented in the Android Gradle Plugin 7.1 Release Notes. Use the following approach instead:

    AGP version lower than 7.1.0

    Open the build.gradle file in your project root directory. Add the Maven repository under allprojects/repositories:

    allprojects {
        repositories {
            ...
            // Add the Maven repository for the ARTC SDK.
            maven { url 'https://maven.aliyun.com/repository/google' }
            maven { url 'https://maven.aliyun.com/repository/public' }
        }
    }
  2. Open app/build.gradle. Add the ARTC SDK dependency under dependencies. Replace \${latest_version} with the actual version number. The latest version is 7.11.0.

    dependencies {
        // Add the ARTC SDK dependency.
        // Replace ${latest_version} with the actual version number.
        implementation 'com.aliyun.aio:AliVCSDK_ARTC:${latest_version}'
        // For version 7.4.0 and earlier, also add the keep dependency.
        // implementation 'com.aliyun.aio.keep:keep:1.0.1'
    }

    For AGP 8.1+, consider migrating to version catalogs. Migrate dependencies to version catalogs.

Manual integration by downloading the SDK

  1. Download the ARTC SDK AAR file from the SDK download section. The latest version is 7.11.0. File name format: AliVCSDK_ARTC-x.y.z.aar.

  2. Copy the AAR file to your project directory (for example, app/libs). Create the directory if needed.

  3. Open settings.gradle in the project root. Add the AAR file directory under dependencyResolutionManagement/repositories:

    dependencyResolutionManagement {
        repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
        repositories {
            google()
            mavenCentral()
            // Add the relative path to the ARTC SDK AAR file.
            flatDir {
                dir 'app/libs'
            }
        }
    }

    For AGP versions below 7.1.0, add the following to allprojects/repositories in build.gradle:

    allprojects {
        repositories {
            ...
            // Add the relative path to the ARTC SDK AAR file.
            flatDir {
                dir 'app/libs'
            }
        }
    }
  4. Open app/build.gradle. Add the AAR dependency under dependencies:

    // Replace x.y.z with the actual version number.
    implementation(name:'AliVCSDK_ARTC', version: 'x.y.z', ext:'aar')
  5. After building, the dependency appears under External Libraries.

    External Libraries
      > < Android API 35, extension level 13 Platform > /Users/wy/Libra...
      > < jbr-21 > /Applications/Android Studio.app/Contents/jbr/Conte...
      > Gradle: :AliVCSDK_ARTC:7.2.0@aar
      > Gradle: androidx.activity:activity:1.8.0@aar

Step 2: Configure CPU architectures

In app/build.gradle, specify supported CPU architectures in the defaultConfig block. Available architectures: armeabi-v7a, arm64-v8a, x86, and x86_64.

android {
    defaultConfig {
        // ...other default configurations
        // Support armeabi-v7a and arm64-v8a architectures.
        ndk {
             abiFilters "armeabi-v7a", "arm64-v8a"
        }
    }
}

Step 3: Create a user interface

Create a user interface for your real-time interaction scenario. For a multi-person video call, create a ScrollView that adds a video view when a user joins and removes it when the user leaves.

Code example

class VideoCallMainVC: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
        self.title = self.channelId
        
        self.setup()
        self.startPreview()
        self.joinChannel()
    }
    
    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        
        self.leaveAnddestroyEngine()
    }
    
    @IBOutlet weak var contentScrollView: UIScrollView!
    var videoViewList: [VideoView] = []

    // Create a video call render view and add it to contentScrollView.
    func createVideoView(uid: String) -> VideoView {
        let view = VideoView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
        view.uidLabel.text = uid
        
        self.contentScrollView.addSubview(view)
        self.videoViewList.append(view)
        self.updateVideoViewsLayout()
        return view
    }

    // Remove a video call render view from contentScrollView.
    func removeVideoView(uid: String) {
        let videoView = self.videoViewList.first { $0.uidLabel.text == uid }
        if let videoView = videoView {
            videoView.removeFromSuperview()
            self.videoViewList.removeAll(where: { $0 == videoView})
            self.updateVideoViewsLayout()
        }
    }
    // Refresh the layout of the subviews in contentScrollView.
    func updateVideoViewsLayout() {
        let margin = 24.0
        let width = (self.contentScrollView.bounds.width - margin * 3.0) / 2.0
        let height = width // width * 16.0 / 9.0
        let count = 2
        for i in 0..<self.videoViewList.count {
            let view = self.videoViewList[i]
            let x = Double(i % count) * (width + margin) + margin
            let y = Double(i / count) * (height + margin) + margin
            view.frame = CGRect(x: x, y: y, width: width, height: height)
        }
        self.contentScrollView.contentSize = CGSize(width: self.contentScrollView.bounds.width, height: margin + Double(self.videoViewList.count / count + 1) * height + margin)
    }
}

Implementation steps

This section explains how to use the ARTC SDK to build a basic real-time audio and video application. Copy the code sample to test 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:

image

The following code sample implements a basic video call:

Basic workflow code sample

class VideoCallMainVC: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // Sets up the view, starts the local preview, and joins the channel.
        self.title = self.channelId

        self.setup()
        self.startPreview()
        self.joinChannel()
    }

    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)

        self.leaveAnddestroyEngine()
    }

    @IBOutlet weak var contentScrollView: UIScrollView!
    var videoViewList: [VideoView] = []

    var channelId: String = ""
    var userId: String = ""

    var rtcEngine: AliRtcEngine? = nil

    var joinToken: String? = nil

    func setup() {

        // Create and initialize the engine.
        let engine = AliRtcEngine.sharedInstance(self, extras:nil)

        // Set the log level.
        engine.setLogLevel(.info)

        // Set the channel profile to interactive mode. In RTC scenarios, always use AliRtcChannelProfile.interactivelive.
        engine.setChannelProfile(AliRtcChannelProfile.interactivelive)
        // Set the user role. The 'host' role (`roleInteractive`) can publish and subscribe. The 'viewer' role (`live`) can only subscribe.
        engine.setClientRole(AliRtcClientRole.roleInteractive)

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

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

        self.rtcEngine = engine
    }

    func joinChannel() {

        // Join the channel with a single-parameter token.
        if let joinToken = self.joinToken {
            let msg =  "JoinWithToken: \(joinToken)"

            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 = "\(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)
            }
            return
        }
    }
    
    func startPreview() {
        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()
    }
    
    func leaveAnddestroyEngine() {
        self.rtcEngine?.stopPreview()
        self.rtcEngine?.leaveChannel()
        AliRtcEngine.destroy()
        self.rtcEngine = nil
    }
    
    // Creates a render view for a user and adds it to the scroll view.
    func createVideoView(uid: String) -> VideoView {
        let view = VideoView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
        view.uidLabel.text = uid
        
        self.contentScrollView.addSubview(view)
        self.videoViewList.append(view)
        self.updateVideoViewsLayout()
        return view
    }
    
    // Removes a video call render view from contentScrollView.
    func removeVideoView(uid: String) {
        let videoView = self.videoViewList.first { $0.uidLabel.text == uid }
        if let videoView = videoView {
            videoView.removeFromSuperview()
            self.videoViewList.removeAll(where: { $0 == videoView})
            self.updateVideoViewsLayout()
        }
    }
    
    // Refreshes the layout of subviews in contentScrollView.
    func updateVideoViewsLayout() {
        let margin = 24.0
        let width = (self.contentScrollView.bounds.width - margin * 3.0) / 2.0
        let height = width // width * 16.0 / 9.0
        let count = 2
        for i in 0..<self.videoViewList.count {
            let view = self.videoViewList[i]
            let x = Double(i % count) * (width + margin) + margin
            let y = Double(i / count) * (height + margin) + margin
            view.frame = CGRect(x: x, y: y, width: width, height: height)
        }
        self.contentScrollView.contentSize = CGSize(width: self.contentScrollView.bounds.width, height: margin + Double(self.videoViewList.count / count + 1) * height + margin)
    }
    
    /*
    // MARK: - Navigation

    // In a storyboard-based application, you will often want to do a little preparation before navigation
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        // Get the new view controller using segue.destination.
        // Pass the selected object to the new view controller.
    }
    */

}

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()
        // 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)
        }
    }
    
    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 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. */
        }
    }
}

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 permissions when a call starts, but for a better user experience, request 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. For token generation rules, see Token Authentication. A token can be generated using either a single-parameter or multi-parameter method, and the joinChannel API you call depends on which 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 sharedInstance method to create an AliRtcEngine instance.

    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 setChannelProfile to set the channel profile to AliRTCInteractiveLive (interactive mode).

      Choose interactive mode for entertainment scenarios or communication mode for one-to-one or group calls.

      Mode

      Publishing

      Subscribing

      Description

      interactive mode

      1. Limited by role. Only users with the host role can publish streams.

      2. Participants can switch roles at any time during the session.

      No role restrictions. All participants have permission to subscribe to streams.

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

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

      1. In communication mode, all participants are aware of each other's presence in the channel.

      2. This mode does not use roles and is functionally equivalent to all users being a host in interactive mode. This simplifies API calls.

    • Call setClientRole to set the user role to AliRTCSdkInteractive (host) or AliRTCSdkLive (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.

    Handle the following callbacks for unrecoverable SDK issues:

    Cause

    Callback and parameters

    Solution

    Description

    Authentication failed

    The result parameter in the onJoinChannelResult callback returns AliRtcErrJoinBadToken.

    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 refreshAuthInfo to 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 same userId.

    • 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 setAudioProfile to 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 publishLocalAudioStream to publish the local audio stream.

  • Call publishLocalVideoStream to publish the local video stream. For an audio-only call, set this to false.

// 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)
Note

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 setLocalViewConfig to set up the local render view and configure local video display properties.

  • Call the startPreview method 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 the single-parameter method via the joinChannel[3/3] interface. Check both the return value and the onJoinChannelResult callback. If both return 0, you have successfully joined the channel. Otherwise, verify that the token is valid.

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)
}
Note
  • 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. Use this callback to set up or remove the remote user's view:

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:

  1. Call stopPreview to stop the local video preview.

  2. Call leaveChannel to leave the channel.

  3. Call destroy to destroy the engine instance and release its resources.

self.rtcEngine?.stopPreview()
self.rtcEngine?.leaveChannel()
AliRtcEngine.destroy()
self.rtcEngine = nil

Step 11: Result

image.pngimage.png

Related documents

data structure

AliRtcEngine API