实现语聊房
本文介绍如何集成语聊 UIKit 来实现语聊房间的创建和销毁、用户进房和退房等功能。
示例项目
API-Examples-AUIKit
是极简语聊项目。你可以参考该项目了解本文提供的集成步骤和代码逻辑。
准备开发环境
前提条件
创建项目
本节介绍如何在 Xcode 创建项目:
-
创建一个新的项目,Application 选择 App,Interface 选择 Storyboard,Language 选择 Swift。
注意如果你没有添加过开发团队信息,会看到 Add account… 按钮。点击该按钮并按照屏幕提示登入 Apple ID,点击 Next,完成后即可选择你的 Apple 账户作为开发团队。
-
为你的项目设置自动签名。本文假设项目名为
AUIKitDemo
。 -
设置部署你的 App 的目标设备。
-
设置 Minimum Deployments 为 iOS 13.0。
-
添加项目的设备权限。在项目导航栏中打开
info.plist
文件,编辑属性列表,添加以下属性:key type value Privacy - Microphone Usage Description String 使用麦克风的目的,例如 for a live interactive streaming
集成依赖
本节介绍如何集成语聊项目所需的依赖库:
-
开始前请确保你已安装 CocoaPods,如尚未安装 CocoaPods,参考 Getting Started with CocoaPods 安装说明。
-
下载声网提供的 AUIVoiceRoom 仓库:
Shellgit clone git@github.com:AgoraIO-Community/AUIVoiceRoom.git
-
将
AUIVoiceRoom/iOS/AScenesKit/AScenesKit
文件夹复制到与你的*.xcodeproj
文件同层级的位置。 -
将
AUIVoiceRoom/iOS/AUIVoiceRoom/AUIVoiceRoom/VoiceChatUIKit.swift
文件复制到与*.xcodeproj
文件同层级的 Demo 文件夹里。具体步骤如下:-
在 Xcode 里打开
*.xcodeproj
文件。选中项目下AUIKitDemo
文件夹,右键选择 Add Files to "AUIKitDemo" ...。 -
指定
AUIVoiceRoom/iOS/AUIVoiceRoom/AUIVoiceRoom/VoiceChatUIKit.swift
文件,勾选 Destination: Copy items if neeed,点击右下角 Add。
-
-
在终端里进入
*.xcodeproj
文件所在目录,并运行pod init
命令。项目文件夹下会生成一个Podfile
文本文件。 -
打开
Podfile
文件,修改文件为如下内容。注意target
和path
的填写。Rubysource 'https://github.com/CocoaPods/Specs.git'
platform :ios, '13.0'
# target 需改为你的项目中实际的 Target 名称
target 'AUIKitDemo' do
use_frameworks!
# path 需为 AScenesKit 依赖库的实际路径
pod 'AScenesKit', :path => './AScenesKit'
# 推荐你指定 RTC SDK 版本为 4.1.1.20
pod 'AgoraRtcEngine_Special_iOS', '4.1.1.20'
end
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0'
end
end
end -
在终端里进入
*.xcodeproj
文件所在目录,运行如下命令安装 AUIKit 依赖:Shellpod update --verbose
安装过程截图如果等待安装的时间太久,请参考加速 Pod。依赖库安装成功后,与
*.xcodeproj
文件所在的同一级目录下会出现一个*.xcworkspace
文件。打开该文件即可体验安装了 AScenesKit 和 AUIKit 等依赖的项目。完成上述步骤后,你的项目文件夹结构如下:
实现语聊房
VoiceChatUIKit
基于声网 RTC、RTM 引擎、AUIKit 等模块封装,帮助你轻松管理语聊房。
你可以使用 VoiceChatUIKit
类中的 API 实现语聊房间的创建和销毁、用户进房和退房等功能。
1. 初始化 VoiceChatUIKit
本节展示初始化 VoiceChatUIKit
的代码逻辑:
-
在
AppDelegate.swift
文件里导入依赖库:Swiftimport AUIKitCore
import AScenesKit -
在
AppDelegate
类中调用VoiceChatUIKit
类的setup
方法并传入如下参数,初始化VoiceChatUIKit
:-
commonConfig
:基础配置,包含如下参数:ownerInfo
:设置你的业务的用户信息。- 声网项目的 App ID。
- 声网项目的 App 证书。
- 用于 HTTP 基本认证的
Authorization
。 - 环信 IM 服务的 App Key。
- 环信 IM 服务的 Client ID。
- 环信 IM 服务的 Client Secret。
- 语聊后端服务的 Host URL。
App ID 等参数取值可参考配置示例项目。
-
apiConfig
:声网 API 实例结构体,包含 RTC 引擎和 RTM 引擎实例。如需使用其中任一一个实例,请通过该结构体进行传入,如果未传入,系统内部会自动创建。
Swiftfunc application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// 生成一个随机的用户 ID
let uid = Int(arc4random_uniform(99999999))
// 将基础配置信息传入 VoiceChatUIKit
let commonConfig = AUICommonConfig()
commonConfig.appId = KeyCenter.AppId
commonConfig.appCert = KeyCenter.AppCertificate
commonConfig.basicAuth = KeyCenter.AppBasicAuth
commonConfig.imAppKey = KeyCenter.IMAppKey
commonConfig.imClientId = KeyCenter.IMClientId
commonConfig.imClientSecret = KeyCenter.IMClientSecret
commonConfig.host = KeyCenter.HostUrl
let ownerInfo = AUIUserThumbnailInfo()
ownerInfo.userId = "\(uid)"
ownerInfo.userName = "user_\(uid)"
ownerInfo.userAvatar = "https://accktvpic.oss-cn-beijing.aliyuncs.com/pic/sample_avatar/sample_avatar_1.png"
commonConfig.owner = ownerInfo
VoiceChatUIKit.shared.setup(commonConfig: commonConfig,
apiConfig: nil)
...
return true
} -
2. 房主创建并加入房间
本节展示如何让房主创建并加入语聊房。
2.1 添加按钮:创建房间
在 ViewController.swift
文件里导入依赖库:
import AUIKitCore
import AScenesKit
在 ViewController
类中,通过 iOS 原生方法增加一个创建房间按钮,用于让房主点击创建。
override func viewDidLoad() {
super.viewDidLoad()
// 作为房主创建房间的按钮
let createButton = UIButton(frame: CGRect(x: 10, y: 100, width: 100, height: 60))
createButton.setTitle("创建房间", for: .normal)
createButton.setTitleColor(.red, for: .normal)
createButton.addTarget(self, action: #selector(onCreateAction), for: .touchUpInside)
view.addSubview(createButton)
}
2.2 声明属性
在 ViewController
类中声明了一个可选属性,用于存储 AUIVoiceChatRoomView
实例以显示语聊房的详情界面。
class ViewController: UIViewController {
var voiceChatView: AUIVoiceChatRoomView?
...
}
2.3 创建房间详情页并启动房间
在 ViewController
中,创建一个 AUIVoiceChatRoomView
实例,并将它设置为 voiceChatView
属性,用于显示语聊房的详情界面。
在 ViewController
的创建房间点击回调(onCreateAction
)里,调用 VoiceChatUIKit
类的 createRoom
方法,并传入以下参数,以启动语聊房:
roomInfo
:房间信息。包含如下:roomId
:房间 ID,每个房间的唯一标识。roomName
:房间名称。不同房间的名称可以重名。owner
:房主信息。
roomConfig
:房间配置,包含房间名、Token 等配置。你可以使用自己的 Token 后台服务生成 Token。voiceChatView
:房间的 UI View。
@objc func onCreateAction(_ button: UIButton) {
// 禁用按钮,防止重复点击
button.isEnabled = false
// 生成一个随机的房间 ID
let roomId = Int(arc4random_uniform(99999999))
// 创建一个 RoomInfo 实例
let roomInfo = AUIRoomInfo()
roomInfo.roomId = "\(roomId)"
roomInfo.roomName = "\(roomId)"
roomInfo.owner = AUIRoomContext.shared.currentUserInfo
let roomConfig = AUIRoomConfig()
// 创建房间容器
let voiceChatView = AUIVoiceChatRoomView(frame: self.view.bounds)
generateToken(channelName: "\(roomId)",
roomConfig: roomConfig,
completion: {[weak self] error in
guard let self = self else { return }
defer {
// 恢复按钮可用状态
button.isEnabled = true
}
if let error = error {
self.navigationController?.popViewController(animated: true)
AUIToast.show(text: error.localizedDescription)
return
}
// 创建房间
VoiceChatUIKit.shared.createRoom(roomInfo: roomInfo,
roomConfig: roomConfig,
chatView: voiceChatView) {[weak self] error in
guard let self = self else { return }
if let error = error {
// 创建房间失败
AUIToast.show(text: error.localizedDescription)
return
}
}
})
self.view.addSubview(voiceChatView)
self.voiceChatView = voiceChatView
}
private func generateToken(channelName: String,
roomConfig: AUIRoomConfig,
completion: @escaping ((Error?) -> Void)) {
let uid = VoiceChatUIKit.shared.commonConfig?.owner?.userId ?? ""
let rtcChorusChannelName = "\(channelName)_rtc_ex"
roomConfig.channelName = channelName
roomConfig.rtcChorusChannelName = rtcChorusChannelName
print("generateTokens: \(uid)")
let group = DispatchGroup()
var err: Error?
group.enter()
let tokenModel1 = AUITokenGenerateNetworkModel()
tokenModel1.channelName = channelName
tokenModel1.userId = uid
tokenModel1.request { error, result in
defer {
if err == nil {
err = error
}
group.leave()
}
guard let tokenMap = result as? [String: String], tokenMap.count >= 2 else { return }
roomConfig.rtcToken = tokenMap["rtcToken"] ?? ""
roomConfig.rtmToken = tokenMap["rtmToken"] ?? ""
}
group.enter()
let tokenModel2 = AUITokenGenerateNetworkModel()
tokenModel2.channelName = rtcChorusChannelName
tokenModel2.userId = uid
tokenModel2.request { error, result in
defer {
if err == nil {
err = error
}
group.leave()
}
guard let tokenMap = result as? [String: String], tokenMap.count >= 2 else { return }
roomConfig.rtcChorusRtcToken = tokenMap["rtcToken"] ?? ""
}
group.notify(queue: DispatchQueue.main) {
completion(err)
}
}
3. (可选)听众加入房间
本节展示如何让听众加入已存在的房间。
3.1 添加按钮:加入房间
在 ViewController
类中,通过 iOS 原生方法增加一个加入房间按钮,用于让听众点击加入。
override func viewDidLoad() {
super.viewDidLoad()
// 作为房主创建房间的按钮
let createButton = UIButton(frame: CGRect(x: 10, y: 100, width: 100, height: 60))
createButton.setTitle("创建房间", for: .normal)
createButton.setTitleColor(.red, for: .normal)
createButton.addTarget(self, action: #selector(onCreateAction), for: .touchUpInside)
view.addSubview(createButton)
// 作为听众加入房间的按钮
let joinButton = UIButton(frame: CGRect(x: 10, y: 160, width: 100, height: 60))
joinButton.setTitle("加入房间", for: .normal)
joinButton.setTitleColor(.red, for: .normal)
joinButton.addTarget(self, action: #selector(onJoinAction), for: .touchUpInside)
view.addSubview(joinButton)
}
3.2 获取房间列表
在 ViewController
类中,调用 VoiceChatUIKit
类的 getRoomInfoList
方法并传入如下参数,以获取语聊房间列表:
lastCreateTime
:房间列表的起始时间(毫秒)。例如,1681879844085。pageSize
:每一页房间列表所展示的房间数量。
如果在房间列表中找到匹配的房间名,并获取到对应的 roomInfo
信息,则执行 enterRoom
函数。
@objc func onJoinAction() {
// 创建 UIAlertController 实例,用于显示弹出对话框
let alertController = UIAlertController(title: "房间名", message: "", preferredStyle: .alert)
alertController.addTextField { (textField) in
textField.placeholder = "请输入"
}
// 创建一个「取消」按钮,并指定点击事件
let cancelAction = UIAlertAction(title: "取消", style: .cancel) { (_) in
}
// 创建一个「确认」按钮,并指定点击事件
let saveAction = UIAlertAction(title: "确认", style: .default) { (_) in
// 获取用户输入的文本内容
if let inputText = alertController.textFields?.first?.text {
// 处理用户输入的内容
VoiceChatUIKit.shared.getRoomInfoList(lastCreateTime: 0, pageSize: 50) { error, roomList in
// 检查是否有错误发生,并确保成功获取到了房间列表
guard let roomList = roomList else { return }
// 遍历房间列表
for room in roomList {
// 比对房间名与用户输入的文本内容是否匹配
if room.roomName == inputText {
// 找到匹配的房间名,执行 enterRoom 函数
self.enterRoom(roomInfo: room)
// 结束循环
break
}
}
}
}
}
// 将「取消」按钮添加到弹出对话框
alertController.addAction(cancelAction)
// 将「确认」按钮添加到弹出对话框
alertController.addAction(saveAction)
// 在当前视图上显示弹出对话框
present(alertController, animated: true, completion: nil)
}
3.3 听众加入房间
在上一步获取到房间信息后,调用 VoiceChatUIKit
类的 enterRoom
方法并传入如下参数,即可加入房间并显示房间数据:
roomId
:房间 ID,每个房间的唯一标识。roomConfig
:房间配置,包含房间名、Token 等配置。你可以使用自己的 Token 后台服务生成 Token。chatView
:房间的 UI View。
func enterRoom(roomInfo: AUIRoomInfo) {
// 创建一个 AUIVoiceChatRoomView 实例,并设置其 frame 为当前视图的边界
let voiceChatView = AUIVoiceChatRoomView(frame: self.view.bounds)
let roomId = roomInfo.roomId
let roomConfig = AUIRoomConfig()
generateToken(channelName: roomId,
roomConfig: roomConfig) {[weak self] err in
guard let self = self else {return}
// 调用 enterRoom 方法加入房间
VoiceChatUIKit.shared.enterRoom(roomId: roomId,
roomConfig: roomConfig,
chatView: self.voiceChatView!) {[weak self] roomInfo, error in
guard let self = self else {return}
if let error = error {
// 如果出现错误,直接返回
self.navigationController?.popViewController(animated: true)
AUIToast.show(text: error.localizedDescription)
return
}
}
}
self.view.addSubview(voiceChatView)
self.voiceChatView = voiceChatView
}
4. (可选)退出或销毁房间
本节展示如何退出并销毁房间。
在语聊房中,房主可以主动销毁房间,房间销毁时听众会被动退出房间。房间存在时,听众可以主动退出房间,也可以被房主踢出房间。下面从主动和被动的角度展示代码逻辑:
4.1 主动退出或销毁房间
在 ViewController
类里 enterRoom
函数中,let voiceChatView = AUIVoiceChatRoomView(frame: self.view.bounds)
代码后添加如下高亮的几行,以实现通过点击按钮,让听众主动退出房间。
func enterRoom(roomInfo: AUIRoomInfo) {
// 创建一个 AUIVoiceChatRoomView 实例,并设置其 frame 为当前视图的边界
let voiceChatView = AUIVoiceChatRoomView(frame: self.view.bounds)
// 听众点击按钮主动退出房间
voiceChatView.onClickOffButton = { [weak self] in
self?.destroyRoom(roomId: roomInfo.roomId)
}
...
}
在 ViewController
类里 onCreateAction
函数中,let voiceChatView = AUIVoiceChatRoomView(frame: self.view.bounds)
代码后添加如下高亮的几行,以实现通过点击按钮,让房主销毁房间。
@objc func onCreateAction(_ button: UIButton) {
...
// 创建房间容器
let voiceChatView = AUIVoiceChatRoomView(frame: self.view.bounds)
// 房主点击按钮销毁房间
voiceChatView.onClickOffButton = { [weak self] in
self?.destroyRoom(roomId: roomInfo.roomId)
}
...
}
4.2 被动退出房间
听众被动退出房间的代码逻辑如下:
-
在
ViewController
类里onCreateAction
函数中,createRoom
方法后添加如下高亮行。调用VoiceChatUIKit
类的bindRespDelegate
方法,订阅房间被销毁的回调。Swift@objc func onCreateAction(_ button: UIButton) {
...
generateToken(channelName: "\(roomId)",
roomConfig: roomConfig,
completion: {[weak self] error in
...
VoiceChatUIKit.shared.createRoom(roomInfo: roomInfo,
roomConfig: roomConfig,
chatView: voiceChatView) {[weak self] error in
...
}
// 订阅房间被销毁回调
VoiceChatUIKit.shared.bindRespDelegate(delegate: self)
})
...
} -
在
ViewController
类里enterRoom
函数中,VoiceChatUIKit.shared.enterRoom(...){...}
代码后添加如下高亮行。调用VoiceChatUIKit
类的bindRespDelegate
方法,订阅房间被销毁的回调。Swiftfunc enterRoom(roomInfo: AUIRoomInfo) {
...
generateToken(channelName: roomId,
roomConfig: roomConfig) {[weak self] err in
guard let self = self else {return}
VoiceChatUIKit.shared.enterRoom(roomId: roomId,
roomConfig: roomConfig,
chatView: self.voiceChatView!) {[weak self] roomInfo, error in
...
}
// 订阅房间被销毁回调
VoiceChatUIKit.shared.bindRespDelegate(delegate: self)
}
...
} -
在
ViewController
文件中,通过AUIVoiceChatRoomServiceRespDelegate
监听如下事件:onRoomDestroy
:房间被房主销毁事件。onRoomUserBeKicked
:听众被房主踢出房间时间。
当监听到这些事件时,执行
destroyRoom
函数。Swiftextension ViewController: AUIVoiceChatRoomServiceRespDelegate {
// 房间销毁回调
@objc func onRoomDestroy(roomId: String) {
self.destroyRoom(roomId: roomId)
}
// 听众被房主踢出房间回调
@objc func onRoomUserBeKicked(roomId: String, userId: String) {
self.destroyRoom(roomId: roomId)
}
} -
在
ViewController
类中,增加destroyRoom
函数。在该函数中,调用VoiceChatUIKit
类的unbindRespDelegate
方法,取消订阅房间被销毁的回调。这样,在房间销毁时,将不再接收房间相关事件。Swiftfunc destroyRoom(roomId: String) {
// 在退出房间时取消订阅,调用 VoiceChatUIKit 的 unbindRespDelegate 方法
VoiceChatUIKit.shared.unbindRespDelegate(delegate: self)
}
4.3 补全销毁逻辑
在主动退出或销毁房间、被动退出房间的情况下,都会执行 destroyRoom
函数。
在 destroyRoom
函数中,添加如下高亮的几行。调用 VoiceChatUIKit
类的 leaveRoom
方法并传入房间 ID,以退出或销毁房间。至此,destroyRoom
函数的代码补全。
func destroyRoom(roomId: String) {
// 点击退出,调用 VoiceChatView 的 onBackAction 方法
self.voiceChatView?.onBackAction()
// 将 VoiceChatView 从父视图中移除
self.voiceChatView?.removeFromSuperview()
// 调用 VoiceChatUIKit 的 leaveRoom 方法退出房间(听众)或销毁房间(主播)
VoiceChatUIKit.shared.leaveRoom(roomId: roomId)
// 在退出房间时取消订阅,调用 VoiceChatUIKit 的 unbindRespDelegate 方法
VoiceChatUIKit.shared.unbindRespDelegate(delegate: self)
}
注意事项
加速 Pod
为加速 Pod 命令的执行结果,建议你使用国内镜像源,步骤如下:
-
打开
Podfile
文件,更改 source 为国内镜像源Shell# 移除官方源
# source 'https://github.com/CocoaPods/Specs.git'
# 使用国内镜像源
source 'https://mirrors.tuna.tsinghua.edu.cn/git/CocoaPods/Specs.git' -
保存并退出
Podfile
,重新执行 Pod 命令安装依赖库。
问题排查
如果在 Xcode 15 编译项目时遇到 Sandbox: rsync.samba(47334) deny(1) file-write-create...
报错信息,你可以依次进行如下操作:
- 在 Xcode 中选中 PROJECT,点击 Build Settings,在 Filter 搜索框中搜索 User Script Sandboxing,将该选项设为 No。
- 选中 TARGETS,检查并确保 User Script Sandboxing 的设置为 No。
后端部署
声网在示例项目中提供的后端服务 Host URL(https://service.shengwang.cn/uikit-v2
)仅供测试和快速集成语聊房使用,请勿用于商业用途。如需商用,请自行部署后端服务,并将客户端代码中的声网后端服务 Host URL 更新为你的服务 Host URL,详见部署后端服务。
下一步
在创建、进入房间后,你可以参考如下业务流程图开发后续的麦位管理、聊天、礼物等功能。