All Products
Search
Document Center

ApsaraVideo Live:Implement a voice chat room on iOS

Last Updated:Nov 19, 2025

This document explains how to integrate the Alibaba Real-Time Communication (ARTC) software development kit (SDK) into your iOS project to create a simple, audio-only interactive application. You can use this application for scenarios such as voice calls and voice chat rooms.

Features

Before you begin, you should understand the following basic terms related to real-time audio and video interaction:

  • ARTC SDK: The software development kit (SDK) for ApsaraVideo Real-time Communication (ARTC). It lets you quickly add real-time audio and video features to your application.

  • Channel: A room where users can interact in real time.

  • Streamer: A user who can publish audio and video streams in a channel and subscribe to streams from other streamers.

  • Viewer: A user who can subscribe to audio and video streams in a channel but cannot publish streams.

The following figure shows the basic flow for an audio call or a voice chat room.

  1. Users must first call joinChannel to join a channel before they can ingest or pull streams.

    • Audio-only call scenario: All users are streamers. They can ingest and pull streams.

    • Voice chat room scenario: Users who need to ingest streams in the channel are set as streamers. Users who only need to pull streams are set as viewers.

    • You can use setClientRole to set different roles for users.

  2. After joining a channel, users have different stream ingest and pull behaviors based on their roles.

    • All users in a channel can receive audio and video streams from other users in the same channel.

    • Streamers can ingest audio and video streams into the channel.

    • If a viewer needs to ingest a stream, you can call the setClientRole method to switch the user's role to streamer. The user can then ingest the stream.

Sample project

The ARTC SDK provides an open source sample project for your reference. You can download or view the sample source code.

Prerequisites

Before you start, make sure your development environment meets the following requirements:

  • Development tools: Xcode 14.0 or later. We recommend using the latest official version.

  • Recommended configuration: CocoaPods 1.9.3 or later.

  • Test device: A test device that runs iOS 9.0 or later.

Note

We recommend using a real device for testing. A simulator may lack certain features.

  • Network environment: A stable network connection is required.

  • Application preparation: Obtain the AppID and AppKey for your ARTC application. For more information, see Create an application.

  • Project creation and configuration: You have created a project, added the required permissions for audio and network interaction, and integrated the ARTC SDK. For more information, see Implement an audio and video call.

Implement audio-only interaction

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. Create and initialize the engine

  • Create the RTC engine

You can call getInstance to create an RTC engine object.

private var rtcEngine: AliRtcEngine? = nil

// Create the engine and set the callback.
let engine = AliRtcEngine.sharedInstance(self, extras:nil)
self.rtcEngine = engine
  • Initialize the engine

    • You can call the setChannelProfile method to set the channel profile to interactive mode.

    • You can call the setClientRole method to set the user's role to streamer or viewer based on the scenario.

    • You can call the setAudioProfile method to set the audio quality and scenario mode.

// Set the channel profile to interactive live. AliRtcInteractivelive is used for all RTC scenarios.
engine.setChannelProfile(AliRtcChannelProfile.interactivelive)
// Set the role.
if self.isAnchor {
    // For streamer mode, which requires ingesting audio and video streams, set the role to AliRtcClientRoleInteractive.
    engine.setClientRole(AliRtcClientRole.roleInteractive)
}
else {
    // For viewer mode, which does not require ingesting audio and video streams, set the role to AliRtcClientRolelive.
    engine.setClientRole(AliRtcClientRole.rolelive)
}

// Set the audio profile. By default, high-quality mode (AliRtcEngineHighQualityMode) and music mode (AliRtcSceneMusicMode) are used.
engine.setAudioProfile(AliRtcAudioProfile.engineHighQualityMode, audio_scene: AliRtcAudioScenario.sceneMusicMode)
  • Implement common callbacks

During operation, the SDK first tries to automatically recover from exceptions using its internal retry mechanism. For errors that cannot be resolved internally, the SDK notifies your application through predefined callbacks.

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

Cause

Callback and parameters

Solution

Description

Authentication failed

The result parameter in the onJoinChannelResult callback returns AliRtcErrJoinBadToken.

If this error occurs, the application needs to check if the token is correct.

When a user calls an API, if authentication fails, the system returns an error message in the API callback.

Token is about to expire

onWillAuthInfoExpire

If this exception occurs, the application needs to get the latest authentication information and then call refreshAuthInfo to refresh it.

A token expiration error can occur in two situations: when a user calls an API or during program execution. Therefore, the error is reported through an API callback or a separate error callback.

Token has expired

onAuthInfoExpired

If this exception occurs, the application needs to have the user rejoin the channel.

A token expiration error can occur in two situations: when a user calls an API or during program execution. Therefore, the error is reported through an API callback or a separate error callback.

Network connectivity error

The onConnectionStatusChange callback returns AliRtcConnectionStatusFailed.

If this exception occurs, the application needs to have the user rejoin the channel.

The SDK can automatically recover from network disconnections within a certain period. However, if the disconnection time exceeds the preset threshold, a timeout occurs and the connection is dropped. In this case, the application should check the network status and guide the user to rejoin the channel.

Kicked offline

onBye

  • AliRtcOnByeUserReplaced: If this exception occurs, check if the user ID is the same.

  • AliRtcOnByeBeKickedOut: If this exception occurs, it means the user was kicked by the business service. The user needs to rejoin the channel.

  • AliRtcOnByeChannelTerminated: If this exception occurs, it means the channel was terminated. The user needs to rejoin the channel.

The RTC service allows an administrator to remove participants.

On-premises device exception

onLocalDeviceException

If this exception occurs, the application needs to check permissions and whether the device hardware is working correctly.

The RTC service supports device detection and diagnostics. When an on-premises device exception occurs, the RTC service notifies the client through a callback. If the SDK cannot resolve the issue, the application needs to 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: You must handle this callback. The token is about to expire. Your application must get a new token for the current channel and user, and then call refreshAuthInfo. */
    }

    func onAuthInfoExpired() {
        "onAuthInfoExpired".printLog()

        /* TODO: You must handle this callback. Prompt the user that the token has expired, and then leave the channel and release the engine. */
    }

    func onBye(_ code: Int32) {
        "onBye code: \(code)".printLog()

        /* TODO: You must handle this callback. Your business may trigger a scenario where different devices with the same user ID compete for access. */
    }

    func onLocalDeviceException(_ deviceType: AliRtcLocalDeviceType, exceptionType: AliRtcLocalDeviceExceptionType, message msg: String?) {
        "onLocalDeviceException deviceType: \(deviceType)  exceptionType: \(exceptionType)".printLog()

        /* TODO: You must handle this callback. We recommend that your application prompts the user about the device error. This callback is triggered only after the SDK has tried all recovery policies and still cannot continue. */
    }

    func onConnectionStatusChange(_ status: AliRtcConnectionStatus, reason: AliRtcConnectionStatusChangeReason) {
        "onConnectionStatusChange status: \(status)  reason: \(reason)".printLog()

        if status == .failed {
            /* TODO: You must handle this callback. We recommend that your application prompts the user. This callback is triggered only after the SDK has tried all recovery policies and still cannot continue. */
        }
        else {
            /* TODO: Optional. Add business logic, usually for data statistics or UI changes. */
        }
    }
}

4. Configure stream ingest and pulling properties

By default, the SDK automatically ingests and pulls audio and video streams in the channel.

  • After you set the role to viewer, the user can only pull streams. The publishLocalAudioStream method becomes invalid.

  • The following configuration can be set for both streamers and viewers.

// Set the audio profile. By default, high-quality mode (AliRtcEngineHighQualityMode) and music mode (AliRtcAudioScenario.sceneMusicMode) are used.
engine.setAudioProfile(AliRtcAudioProfile.engineHighQualityMode, audio_scene: AliRtcAudioScenario.sceneMusicMode)

// For a voice chat scenario, you do not need to publish a video stream.
engine.publishLocalVideoStream(false)

// Set the default subscription to remote audio streams.
engine.setDefaultSubscribeAllRemoteAudioStreams(true)
engine.subscribeAllRemoteAudioStreams(true)

5. Join a channel to start the audio-only interaction

You can call the joinChannel method to join a channel.

Note:

If the token is generated using the single-parameter rule, you must call the single-parameter joinChannel[1/3] method of the SDK. If the token is generated using the multi-parameter rule, you must call the multi-parameter joinChannel[2/3] method of the SDK. After you call the method to join the channel, you can retrieve the result from the onJoinChannelResult callback. If the result is 0, the user successfully joined the channel. Otherwise, check whether the token is valid.

self.rtcEngine?.joinChannel(joinToken, channelId: nil, userId: nil, name: nil) 

6. End the audio-only interaction

When the audio interaction ends, you need to leave the channel and destroy the engine. To end the audio and video interaction, perform the following steps:

  1. Call leaveChannel to leave the channel.

  2. Call destroy to destroy the engine and release its resources.

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

7. (Optional) Let viewers go on and off mic

In your business scenario, if a user with the viewer role wants to ingest a stream, you can call the setClientRole method to switch the role to streamer.

// Switch to the streamer role.
self.rtcEngine?.setClientRole(AliRtcClientRole.roleInteractive)

// Switch to the viewer role.
self.rtcEngine?.setClientRole(AliRtcClientRole.rolelive)

References