Integrate the ApsaraVideo Real-time Communication (ARTC) SDK into your iOS project to build audio-only interactive applications such as voice calls and voice chat rooms.
Key concepts
| Term | Definition |
|---|---|
| ARTC SDK | The SDK for ApsaraVideo Real-time Communication (ARTC). It adds real-time audio and video capabilities to your application. |
| Channel | A virtual space where users interact in real time. |
| Streamer | A user who can publish and subscribe to audio and video streams in a channel. Maps to AliRtcClientRole.roleInteractive in the SDK. |
| Viewer | A user who can subscribe to audio and video streams in a channel but cannot publish streams. Maps to AliRtcClientRole.rolelive in the SDK. |
How channels, streamers, and viewers interact
All users call joinChannel to join a channel before publishing or subscribing to streams.
| Scenario | Roles | Behavior |
|---|---|---|
| Audio-only call | All users are streamers | Each user can publish and subscribe to streams. |
| Voice chat room | Streamers publish streams; viewers only subscribe | Call setClientRole to assign roles. |
After joining a channel:
All users can receive audio and video streams from other users in the same channel.
Only streamers can publish audio and video streams into the channel.
To let a viewer publish a stream, call
setClientRoleto switch the user's role to streamer.
Sample project
Download or view the sample source code from the open-source ARTC SDK sample project.
Prerequisites
Before you begin, make sure you have:
Xcode 14.0 or later installed. Use the latest official version.
CocoaPods 1.9.3 or later installed.
A physical test device running iOS 9.0 or later.
A stable network connection.
An ARTC AppID and AppKey. For more information, see Create an application.
A project configured with the ARTC SDK, including audio and network permissions. For more information, see Implement an audio and video call.
Use a physical device for testing. Simulators may lack certain features.
Implement audio-only interaction
1. Request permissions
Check camera and microphone permissions before starting a call. The SDK checks permissions automatically, but requesting them early ensures 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 that verifies the user's identity. A token can be generated using a single-parameter method or a multi-parameter method. The method determines which joinChannel API to call. For details on token generation, see Implement token-based authentication.
Production environments
Generate the token on your server and send it to the client. Hardcoding the AppKey on the client side poses a security risk.
Development and debugging
If your server does not yet generate tokens, temporarily use the token generation logic from the APIExample. The reference code 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
Call sharedInstance 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 = engineInitialize the engine
Configure the channel profile, user role, and audio settings:
| Method | Purpose | Value |
|---|---|---|
setChannelProfile | Set channel profile to interactive live streaming | AliRtcChannelProfile.interactivelive |
setClientRole | Assign user role | Streamer: AliRtcClientRole.roleInteractive; Viewer: AliRtcClientRole.rolelive |
setAudioProfile | Set audio quality and scenario mode | AliRtcAudioProfile.engineHighQualityMode, AliRtcAudioScenario.sceneMusicMode |
// Set the channel profile to interactive live streaming. AliRtcInteractivelive is used for all RTC scenarios.
engine.setChannelProfile(AliRtcChannelProfile.interactivelive)
// Set the role.
if self.isAnchor {
// For streamer mode, which requires publishing audio and video streams, set the role to AliRtcClientRoleInteractive.
engine.setClientRole(AliRtcClientRole.roleInteractive)
}
else {
// For viewer mode, which does not require publishing 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)4. Configure publishing and subscription
By default, the SDK automatically publishes and subscribes to audio and video streams in the channel.
After the role is set to viewer, the
publishLocalAudioStreammethod becomes invalid.The following configuration applies to 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
Call joinChannel to join a channel and start the audio-only interaction.
If the token was generated using the single-parameter method, call the single-parameter joinChannel[1/3] method. If the token was generated using the multi-parameter method, call the multi-parameter joinChannel[2/3] method. The onJoinChannelResult callback returns the result. A result of 0 indicates success. Any other value indicates a failure -- check whether the token is valid.
self.rtcEngine?.joinChannel(joinToken, channelId: nil, userId: nil, name: nil)6. End the interaction
Leave the channel and destroy the engine to release resources:
Call
leaveChannelto leave the channel.Call
destroyto destroy the engine and release resources.
self.rtcEngine?.leaveChannel()
AliRtcEngine.destroy()
self.rtcEngine = nil7. (Optional) Switch between viewer and streamer roles
Call setClientRole to switch a viewer to streamer (to publish streams) or a streamer back to viewer (to stop publishing).
// Switch to the streamer role.
self.rtcEngine?.setClientRole(AliRtcClientRole.roleInteractive)
// Switch to the viewer role.
self.rtcEngine?.setClientRole(AliRtcClientRole.rolelive)Handle engine callbacks
The SDK tries to recover from exceptions automatically. For errors that cannot be resolved internally, the SDK notifies your application through callbacks.
| Cause | Callback and parameters | Solution |
|---|---|---|
| Authentication failed | onJoinChannelResult returns AliRtcErrJoinBadToken | Check whether the token is correct. |
| Token is about to expire | onWillAuthInfoExpire | Get the latest authentication information, then call refreshAuthInfo. |
| Token has expired | onAuthInfoExpired | Have the user rejoin the channel. |
| Network connectivity error | onConnectionStatusChange returns AliRtcConnectionStatusFailed | The SDK can recover from brief network disconnections automatically. If the disconnection exceeds the timeout threshold, check the network status and have the user rejoin the channel. |
| Kicked offline | onBye | AliRtcOnByeUserReplaced: Check if the user ID is duplicated. AliRtcOnByeBeKickedOut: The user was kicked by the service. Rejoin the channel. AliRtcOnByeChannelTerminated: The channel was terminated. Rejoin the channel. |
| Local device exception | onLocalDeviceException | Check permissions and device hardware 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. */
}
}
}Related operations
Common audio operations and configurations -- in-ear monitoring, volume control, and speaker callbacks.
Set voice changers, reverberation, and beautification -- vocal effects such as voice changers and reverberation.
Play and ingest external audio input (including sound effects and accompaniment) -- background music and accompaniment audio files.