All Products
Search
Document Center

ApsaraVideo Live:Implement audio and video calls on iOS

Last Updated:Dec 06, 2025

This guide shows how to integrate the Alibaba Real-Time Communication (ARTC) software development kit (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:

image
  1. Call setChannelProfile to set the scenario, and call joinChannel to 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 setClientRole before 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.

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

Sample project

The ARTC SDK provides an open-source sample project for real-time audio and video applications.

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.

Note

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.

  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.

image.png

  1. Configure your project settings as needed, including the Bundle Identifier, Signing, and Minimum Deployments.

Configure your project

Step 1: Import SDK

Automatic integration through CocoaPods (recommended)

  1. Open Terminal and install CocoaPods. If you have already installed it, skip this step.

sudo gem install cocoapods
  1. Open Terminal, navigate to the root directory of your project, and run the following command to create a Podfile.

pod init
  1. Open and edit the generated Podfile to add the RTC SDK dependency. The latest available version is 7.8.1.

target 'MyApp' do
  use_frameworks!
  # Replace ${latest version} with a specific version number.
  pod 'AliVCSDK_ARTC', '~> ${latest version}'
end
  1. In Terminal, run the following command to update the CocoaPods dependency library in your project.

pod install
  1. After 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.

    image

Manual integration

  1. Download the SDK, obtain the latest ARTC SDK package, and decompress it.

  2. Copy the framework files from the decompressed package to your project directory.

  3. Open your project in Xcode, select File -> Add Files to "xxx" from the menu, and add the SDK library files to your project.

    image

  4. Select the target and set the imported framework files to "Embed & Sign".

image

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.

image.png

  • Enable audio background mode (optional).

As shown in the figure, check Audio, AirPlay, and Picture in Picture.

image.png

Step 3: Create a user interface

Create a user interface that is suitable for your real-time interaction scenario. For example, in a multi-person video call, you can create a ScrollView. When a user joins the call, you can add a video view to this container. When a user leaves, you can remove their video view and refresh the layout.

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

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:

image

The following is the complete reference code for a basic video call:

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] = []

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

        // Set the audio profile. The default is high-quality mode (AliRtcEngineHighQualityMode) and music scenario (AliRtcSceneMusicMode).
        engine.setAudioProfile(AliRtcAudioProfile.engineHighQualityMode, audio_scene: AliRtcAudioScenario.sceneMusicMode)

        // Set the video encoding parameters.
        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)

        // By default, the SDK publishes the video stream. Calling publishLocalVideoStream(true) is optional.
        engine.publishLocalVideoStream(true)
        // By default, the SDK publishes the audio stream. For a video call, calling publishLocalAudioStream(true) is optional.
        // For an audio-only call, call publishLocalVideoStream(false) to stop publishing video.
        engine.publishLocalAudioStream(true)

        // Set the default to subscribe to remote audio and video streams.
        engine.setDefaultSubscribeAllRemoteAudioStreams(true)
        engine.subscribeAllRemoteAudioStreams(true)
        engine.setDefaultSubscribeAllRemoteVideoStreams(true)
        engine.subscribeAllRemoteVideoStreams(true)

        self.rtcEngine = engine
    }

    func joinChannel() {

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

For the complete sample code, see Run the ARTC demo 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 more information about how the token is generated, see 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.
    */
    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()
    }

}

3. Import the ARTC SDK component

// Import the ARTC module.
import AliVCSDK_ARTC

4. Create and initialize the engine

  • Create the RTC engine

    Call the <a baseurl="t2974573_v2_2_1.xdita" data-node="4087266" data-root="16090" data-tag="xref" href="t2309850.xdita#2db21d4f16s77" id="ec8b2fb525ugj">sharedInstance</a> 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 to AliRTCInteractiveLive (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 calls. Choosing the right mode ensures a smooth user experience and efficient use of network resources.

      Mode

      Publishing

      Subscribing

      Description

      Interactive mode

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

      2. Participants can flexibly switch roles throughout the session.

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

      1. In interactive mode, events such as a streamer joining or leaving the channel, or starting to publish a stream, are notified to viewers in real time. This ensures viewers are aware of the streamer's status. Conversely, viewer activities are not notified to streamers, which ensures an uninterrupted stream.

      2. In this mode, streamers are responsible for interaction, while viewers mainly consume content and do not typically participate in the interaction. If your business needs might change, consider using interactive mode by default. Its flexibility lets you 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.

      1. In communication mode, participants are aware of each other's presence.

      2. Although this mode does not differentiate user roles, it is functionally equivalent to the streamer role in interactive mode. The goal is to simplify operations, which lets users achieve the desired functionality with fewer API calls.

    • Call setClientRole to set the user role to AliRTCSdkInteractive (streamer) or AliRTCSdkLive (viewer). Note that the streamer role publishes and subscribes to streams by default. The viewer role only subscribes and has local preview and publishing disabled by default.

      Note: When a user switches from streamer 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 streamer, 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 first attempts to recover automatically using its internal retry mechanisms. For errors that it cannot resolve, the SDK notifies your application through predefined callback interfaces.

    The following are key callbacks for issues that the SDK cannot handle. Your application must listen for and respond to these callbacks:

    Cause of exception

    Callback and parameters

    Solution

    Description

    Authentication failed

    result in onJoinChannelResult returns AliRtcErrJoinBadToken

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

    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

    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.

    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 setAudioProfile to 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 propertiesSet properties for the published video stream, such as resolution, bitrate, and frame rate.

    You can set properties for the published video stream, such as resolution, bitrate, and frame rate.

    // Set the video encoding parameters.
    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 and video streams and set the default behavior to subscribe to all users' streams:

  • Call publishLocalAudioStream to publish an audio stream.

  • Call publishLocalVideoStream to publish a video stream. For an audio-only call, you can set this to false.

// By default, the SDK publishes the video stream. Calling publishLocalVideoStream(true) is optional.
engine.publishLocalVideoStream(true)
// By default, the SDK publishes the audio stream. For a video call, calling publishLocalAudioStream(true) is optional.
// For an audio-only call, call publishLocalVideoStream(false) to stop publishing video.
engine.publishLocalAudioStream(true)

// Set the default to subscribe to 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 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 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()

8. Join a channel

Call joinChannel to join the channel. We recommend using the single-parameter method, which requires calling the joinChannel[3/3] operation. After you call joinChannel, check both the return value and the result in the onJoinChannelResult callback. If the return value is 0 and the result in the callback is also 0, the user has successfully joined the channel. Otherwise, verify that the token is valid.

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)
}
Note
  • After a user joins the 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 that 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. The following is the 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:

  1. Call stopPreview to stop the video preview.

  2. Call leaveChannel to leave the channel.

  3. Call destroy to destroy the engine and release all related resources.

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

11. Demo

image.pngimage.png

References

Data structures

AliRtcEngine class