本文档将介绍如何在您的 iOS 项目中集成 ARTC SDK, 快速实现一个简单的纯音频互动App,适用于语音通话、语聊房等场景。
功能介绍
在开始前,您需要了解以下有关音视频实时互动的基本概念:
ARTC SDK:阿里云实时音视频产品,帮助开发中快速实现实时音视频互动的SDK。
频道:房间的概念,在同一个频道内的用户可以进行实时互动。
主播:可在频道内发布音视频流,并可订阅其他主播发布的音视频流。
观众:可在频道内订阅音视频流,不能发布音视频流。
下图展示了实现音频通话及语聊房的基本流程:
用户需要先调用
joinChannel加入频道,才能进行推流、拉流:普通纯音频通话场景:所有用户都是主播角色,可以进行推流和拉流;
语聊房场景:需要在频道内推流的用户设置主播角色;如果用户只需要拉流,不需要推流,则设置观众角色;
通过
setClientRole为用户设置不同的角色。
加入频道后,不同角色的用户有不同的推拉流行为:
所有频道内的用户都可以接收相同频道内的音视频流;
主播角色可以在频道内推音视频流;
观众如果需要推流,需要调用
setClientRole方法,将用户角色切换成主播,便可以推流。
示例项目
阿里云ARTC SDK提供了开源的示例项目供客户参考,您可以前往下载或查看示例源码。
前提条件
在实现功能以前,请确保您的开发环境满足:
开发工具:Xcode 14.0 及以上版本,推荐使用最新正式版本。
配置推荐:CocoaPods 1.9.3 及以上版本。
测试设备:iOS 9.0 及以上版本的测试设备。
实现步骤
下面将以语聊房场景为例进行演示,相关功能时序如下:
语聊房场景主要特点如下:
纯音频:频道内仅包含音频,不包含视频。
主播/观众角色:频道内角色分为主播和观众角色,主播角色可以推拉音频流,观众角色只能拉取主播推送的音频流;观众角色可以切换为主播角色。
实现纯音频互动
1、申请权限请求
进入音视频通话时,虽然SDK会检查是否已在App中授予了所需要的权限,当为保障体验,建议在发起通话前检查视频拍摄及麦克风采集的权限。
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)
}
}
// 使用示例
checkMicrophonePermission { granted in
if granted {
print("用户已授权麦克风")
} else {
print("用户未授权麦克风")
}
}
checkCameraPermission { granted in
if granted {
print("用户已授权摄像头")
} else {
print("用户未授权摄像头")
}
}
2、鉴权Token
加入ARTC频道需要一个鉴权Token,用于鉴权用户的合法身份,其鉴权Token生成规则参见:Token鉴权。Token 生成有两种方式:单参数方式和多参数方式,不同的Token生成方式需要调用SDK不同的加入频道(joinChannel)的接口。
上线发布阶段:
由于Token的生成需要使用AppKey,写死在客户端存在泄漏的风险,因此强烈建议线上业务通过业务Server生成下发给客户端。
开发调试阶段:
开发调试阶段,如果业务Server还没有生成Token的逻辑,可以暂时参考APIExample上的Token生成逻辑,生成临时Token,其参考代码如下:
class ARTCTokenHelper: NSObject {
/**
* RTC AppId
*/
public static let AppId = "<RTC AppId>"
/**
* RTC AppKey
*/
public static let AppKey = "<RTC AppKey>"
/**
* 根据channelId,userId, timestamp 生成多参数入会的 token
* Generate a multi-parameter meeting token 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
}
/**
* 根据channelId,userId, nonce 生成单参数入会 的token
* Generate a single-parameter meeting token 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 ""
}
/**
* 字符串签名
* String signing (SHA256)
*/
private static func GetSHA256(_ input: String) -> String {
// 将输入字符串转换为数据
let data = Data(input.utf8)
// 创建用于存储哈希结果的缓冲区
var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
// 计算 SHA-256 哈希值
data.withUnsafeBytes {
_ = CC_SHA256($0.baseAddress, CC_LONG(data.count), &hash)
}
// 将哈希值转换为十六进制字符串
return hash.map { String(format: "%02hhx", $0) }.joined()
}
}
3. 创建并初始化引擎
创建 RTC 引擎
调用getInstance创建 RTC 引擎对象。
private var rtcEngine: AliRtcEngine? = nil
// 创建引擎并设置回调
let engine = AliRtcEngine.sharedInstance(self, extras:nil)
self.rtcEngine = engine初始化引擎
调用
setChannelProfile接口设置频道为互动模式。根据业务场景中用户的角色,调用
setClientRole接口为用户设置主播/观众角色。调用
setAudioProfile接口设置音频质量与场景模式。
// 设置频道模式为互动模式,RTC下都使用AliRtcInteractivelive
engine.setChannelProfile(AliRtcChannelProfile.interactivelive)
// 设置角色
if self.isAnchor {
// 主播模式,需要推音视频流,设置AliRtcClientRoleInteractive
engine.setClientRole(AliRtcClientRole.roleInteractive)
}
else {
// 观众模式,不需要推音视频流,设置AliRtcClientRolelive
engine.setClientRole(AliRtcClientRole.rolelive)
}
// 设置音频Profile,默认使用高音质模式AliRtcEngineHighQualityMode及音乐模式AliRtcSceneMusicMode
engine.setAudioProfile(AliRtcAudioProfile.engineHighQualityMode, audio_scene: AliRtcAudioScenario.sceneMusicMode)实现常用回调
SDK 在运行过程中如遇到异常情况,会优先尝试内部重试机制以自动恢复。对于无法自行解决的错误,SDK 会通过预定义的回调接口通知您的应用程序。
以下是一些 SDK 无法处理、需由应用层监听和响应的关键回调:
异常发生原因 | 回调及参数 | 解决方案 | 说明 |
鉴权失败 |
| 发生错误时App需要检查Token是否正确。 | 在用户主动调用API时,若鉴权失败,系统将在调用API的回调中返回鉴权失败的错误信息。 |
鉴权将要过期 |
| 发生该异常时App需要重新获取最新的鉴权信息后,再调用 | 鉴权过期错误在两种情况下出现:用户调用API或程序执行期间。因此,错误反馈将通过API回调或通过独立的错误回调通知。 |
鉴权过期 |
| 发生该异常时App需要重新入会。 | 鉴权过期错误在两种情况下出现:用户调用API或程序执行期间。因此,错误反馈将通过API回调或通过独立的错误回调通知。 |
网络连接异常 |
| 发生该异常时APP需要重新入会。 | SDK具备一定时间断网自动恢复能力,但若断线时间超出预设阈值,会触发超时并断开连接。此时,App应检查网络状态并指导用户重新加入会议。 |
被踢下线 |
|
| RTC服务提供了管理员可以主动移除参与者的功能。 |
本地设备异常 |
| 发生该异常时App需要检测权限、设备硬件是否正常。 | RTC服务支持设备检测和异常诊断的能力;当本地设备发生异常时,RTC服务会通过回调的方式通知客户本地设备异常,此时,若SDK无法自行解决问题,则App需要介入以查看设备是否正常。 |
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) {
// 远端用户的上线
"onRemoteUserOlineNotify uid: \(uid)".printLog()
}
func onRemoteUserOffLineNotify(_ uid: String, offlineReason reason: AliRtcUserOfflineReason) {
// 远端用户的下线
"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: 务必处理;Token即将过期,需要业务触发重新获取当前channel,user的鉴权信息,然后设置refreshAuthInfo即可 */
}
func onAuthInfoExpired() {
"onAuthInfoExpired".printLog()
/* TODO: 务必处理;提示Token失效,并执行离会与释放引擎 */
}
func onBye(_ code: Int32) {
"onBye code: \(code)".printLog()
/* TODO: 务必处理;业务可能会触发同一个UserID的不同设备抢占的情况 */
}
func onLocalDeviceException(_ deviceType: AliRtcLocalDeviceType, exceptionType: AliRtcLocalDeviceExceptionType, message msg: String?) {
"onLocalDeviceException deviceType: \(deviceType) exceptionType: \(exceptionType)".printLog()
/* TODO: 务必处理;建议业务提示设备错误,此时SDK内部已经尝试了各种恢复策略已经无法继续使用时才会上报 */
}
func onConnectionStatusChange(_ status: AliRtcConnectionStatus, reason: AliRtcConnectionStatusChangeReason) {
"onConnectionStatusChange status: \(status) reason: \(reason)".printLog()
if status == .failed {
/* TODO: 务必处理;建议业务提示用户,此时SDK内部已经尝试了各种恢复策略已经无法继续使用时才会上报 */
}
else {
/* TODO: 可选处理;增加业务代码,一般用于数据统计、UI变化 */
}
}
}4. 设置推拉流属性
SDK 默认情况下会自动推送和拉取频道内的音视频流
设置为观众模式后只能拉流,publishLocalAudioStream 无效
对于主播和观众均可以设置为下面的配置
// 设置音频Profile,默认使用高音质模式AliRtcEngineHighQualityMode及音乐模式AliRtcSceneMusicMode
engine.setAudioProfile(AliRtcAudioProfile.engineHighQualityMode, audio_scene: AliRtcAudioScenario.sceneMusicMode)
// 语聊场景,不需要publish视频
engine.publishLocalVideoStream(false)
// 设置默认订阅远端的音频
engine.setDefaultSubscribeAllRemoteAudioStreams(true)
engine.subscribeAllRemoteAudioStreams(true)5. 加入频道开始纯音频互动
调用joinChannel接口加入频道。
注意:
如果token是单参数规则生成的,需要调用SDK单参数的joinChannel[1/3]接口,如果是多参数规则生成的,需要调用SDK多参数的joinChannel[2/3]接口。调用完加入频道后,可以在onJoinChannelResult回调中拿到加入频道结果,如果result为0,则表示加入频道成功,否则需要检查传进来的Token是否非法。
self.rtcEngine?.joinChannel(joinToken, channelId: nil, userId: nil, name: nil) 6. 结束纯音频互动
音频互动结束,需要离开房间并销毁引擎,按照下列步骤结束音视频互动
调用
leaveChannel离会。调用
destroy销毁引擎,并释放相关资源。
self.rtcEngine?.leaveChannel()
AliRtcEngine.destroy()
self.rtcEngine = nil7. (可选)观众上下麦
业务场景中,如果观众角色的用户想要推流,需要调用setClientRole将观众角色切换为主播角色。
// 切换为主播角色
self.rtcEngine?.setClientRole(AliRtcClientRole.roleInteractive)
// 切换为观众角色
self.rtcEngine?.setClientRole(AliRtcClientRole.rolelive)相关文档
有关音频的更多操作,例如耳返、音量和说话人回调等,请参考音频常用操作和配置。
设置人声效果,例如变声、美声、混响等,请参考设置变声、混响、美声。
如果需要播放背景音乐、伴奏音乐文件等,请参考播放与推流外部输入音频(包括音效、伴奏)。