diff --git "a/markdown/chatroom/01-\350\277\233\345\207\272\346\210\277\351\227\264.wsd" "b/markdown/chatroom/01-\350\277\233\345\207\272\346\210\277\351\227\264.wsd" new file mode 100644 index 00000000000..e03c28b3641 --- /dev/null +++ "b/markdown/chatroom/01-\350\277\233\345\207\272\346\210\277\351\227\264.wsd" @@ -0,0 +1,34 @@ +@startuml +title 用户进出语聊房的流程图 +start +: 进入语聊房列表; +: 初始化信令系统; +: 登录信令系统; +if (是否需要创建房间) then (Yes) + : 创建房间; +else(No) +endif +:进入房间; +fork + :初始化 RTC 引擎; + :[RTC] 加入频道; + fork again + :[信令] 加入聊天室; + endfork +:显示聊天消息和声音; +if (是否离开房间) then (Yes) +fork +:[RTC] 退出频道; +:销毁 RTC 引擎; +fork again +:[信令] 离开聊天室; +endfork +if (角色是否为房主) then (Yes) +:销毁房间; +else (No) +:离开房间; +endif +else (No) +endif +stop +@enduml \ No newline at end of file diff --git "a/markdown/chatroom/02-\346\210\277\344\270\273\351\202\200\350\257\267.wsd" "b/markdown/chatroom/02-\346\210\277\344\270\273\351\202\200\350\257\267.wsd" new file mode 100644 index 00000000000..9ec1e6bcb77 --- /dev/null +++ "b/markdown/chatroom/02-\346\210\277\344\270\273\351\202\200\350\257\267.wsd" @@ -0,0 +1,26 @@ +@startuml +title 房主邀请听众上麦的流程图 +start +if (角色是否为房主) then (Yes) + :房主向听众发起上麦邀请; + :房主收到听众对邀请的响应; + if (听众同意房主的邀请) then (Yes) + :房主收到麦位已更新的通知; + else (No) + stop + endif +else(No) + :听众收到房主的上麦邀请; + if (听众是否同意房主的邀请) then (Yes) + :听众发起同意邀请的通知; + :上麦并修改麦位信息; + :[RTC] 更新当前用户角色为主播; + :[RTC] 发布本地音频流; + else (No) + :发起拒绝邀请的通知; + stop + endif +endif +:更新麦位 UI 界面; +stop +@enduml \ No newline at end of file diff --git "a/markdown/chatroom/03-\350\247\202\344\274\227\347\224\263\350\257\267.wsd" "b/markdown/chatroom/03-\350\247\202\344\274\227\347\224\263\350\257\267.wsd" new file mode 100644 index 00000000000..3224fc89bbe --- /dev/null +++ "b/markdown/chatroom/03-\350\247\202\344\274\227\347\224\263\350\257\267.wsd" @@ -0,0 +1,21 @@ +@startuml +title 听众向房主申请上麦的流程图 +start +if (角色是否为房主) then (Yes) + :房主收到听众的上麦申请; + if (房主是否同意申请) then (Yes) + :房主发送接受听众上麦申请的通知; + :房主修改麦位信息以让听众上麦; + else (No) + :房主发送拒绝听众上麦申请的通知; + stop + endif +else(No) + :听众向房主发起上麦申请; + :房主接受后,听众收到自己上麦的消息; + :[RTC] 更新当前用户角色为主播; + :[RTC] 发布本地音频流; +endif +:更新麦位 UI 界面; +stop +@enduml \ No newline at end of file diff --git a/markdown/chatroom/AUIVoiceRoom/chatroom_pass_overview.md b/markdown/chatroom/AUIVoiceRoom/chatroom_pass_overview.md new file mode 100644 index 00000000000..2d4e9f4a5c9 --- /dev/null +++ b/markdown/chatroom/AUIVoiceRoom/chatroom_pass_overview.md @@ -0,0 +1,9 @@ +声动语聊解决方案提供了同时支持含 UI 和无 UI 组件的集成方案。 + +无 UI 组件的语聊集成方案是 PaaS 方案。该方案通过集成声网的实时互动(RTC)、即时通讯(IM)、云服务(Service)产品,提供房间管理、麦位控制、聊天打赏、音频特效等核心功能。 + +相较于含 UI 组件的集成方案,无 UI 组件的语聊集成方案具有以下特点和优势: + +- 灵活开发:交互逻辑完全在客户端实现,修改业务逻辑时无需依赖后端介入,更加灵活。 +- 全面功能演示:含有最新的功能迭代,提供更全面的效果演示能力。 +- 丰富最佳实践参考:积累了丰富的语聊集成最佳实践,为开发者提供更多可参考的代码逻辑。 diff --git a/markdown/chatroom/AUIVoiceRoom/input-and-ktv.md b/markdown/chatroom/AUIVoiceRoom/input-and-ktv.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/markdown/chatroom/AUIVoiceRoom/input-and.md b/markdown/chatroom/AUIVoiceRoom/input-and.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/markdown/chatroom/AUIVoiceRoom/input-ios.md b/markdown/chatroom/AUIVoiceRoom/input-ios.md new file mode 100644 index 00000000000..966616c8894 --- /dev/null +++ b/markdown/chatroom/AUIVoiceRoom/input-ios.md @@ -0,0 +1,355 @@ +`VoiceChatUIKit` 基于声网 RTC、RTM 引擎、AUIKit 等模块封装,帮助你轻松管理语聊房。你可以使用 `VoiceChatUIKit` 类中的 API 实现语聊房间的创建、用户进房、退出和销毁等功能。 + +### 1. 初始化 VoiceChatUIKit + +本节展示初始化 `VoiceChatUIKit` 的代码逻辑: + +1. 在 `AppDelegate.swift` 文件里导入依赖库: + + ```swift + import AUIKitCore + import AScenesKit + ``` + +2. 在 `AppDelegate` 类中调用 `VoiceChatUIKit` 类的 `setup` 方法并传入如下参数,初始化 `VoiceChatUIKit`: + + - `roomConfig`:通用配置,包含后端域名地址、用户 ID、用户名、用户头像。 + - `rtcEngine`:声网 RTC 引擎。 + - `rtcClient`:声网 RTM 引擎。 + + ```swift + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // 生成一个随机的用户 ID + let uid = Int(arc4random_uniform(99999999)) + + // 创建一个 AUICommonConfig 实例 + let commonConfig = AUICommonConfig() + + // 设置 AUICommonConfig 的属性值 + commonConfig.host = "https://service.agora.io/uikit-voiceroom" // 设置主机地址 + commonConfig.userId = "\(uid)" // 设置用户 ID + commonConfig.userName = "user_\(uid)" // 设置用户名 + commonConfig.userAvatar = "https://accktvpic.oss-cn-beijing.aliyuncs.com/pic/sample_avatar/sample_avatar_1.png" // 设置用户头像 + + // 调用 VoiceChatUIKit 的 setup 方法进行初始化设置 + VoiceChatUIKit.shared.setup(roomConfig: commonConfig, + rtcEngine: nil, // 如果有外部初始化的 rtc engine,可以传入该实例 + rtmClient: nil) // 如果有外部初始化的 rtm client,可以传入该实例 + + // 在应用程序启动完成后的自定义逻辑(可根据需要进行覆盖) + ... + + return true + } + ``` + +### 2. 房主创建房间 + +本节展示如何让房主创建语聊房。 + +#### 添加按钮:创建房间 + +在 `ViewController.swift` 文件里导入依赖库: + +```swift +import AUIKitCore +import AScenesKit +``` + +在 `ViewController` 类中,通过 iOS 原生方法增加一个「创建房间」按钮,用于让房主点击创建。 + +```swift +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) +} +``` + +#### 创建语聊房 + +在 `ViewController` 类中,调用 `VoiceChatUIKit` 类的 `createRoom` 并传入 `roomInfo` 参数以创建语聊房。`roomInfo` 里需传入房间信息。 + +如果创建房间成功,那么执行 `enterRoom` 函数。 + + +```swift +@objc func onCreateAction(_ button: UIButton) { + // 生成一个随机的房间 ID + let roomId = Int(arc4random_uniform(99999999)) + // 创建一个 AUICreateRoomInfo 实例 + let room = AUICreateRoomInfo() + // 设置房间名为生成的随机房间 ID + room.roomName = "\(roomId)" + // 禁用按钮,防止重复点击 + button.isEnabled = false + + // 调用 VoiceChatUIKit 的 createRoom 方法创建房间 + VoiceChatUIKit.shared.createRoom(roomInfo: room) { roomInfo in + // 成功创建房间后的回调 + self.enterRoom(roomInfo: roomInfo!) // 加入房间 + button.isEnabled = true // 恢复按钮可用状态 + } failure: { error in + // 创建房间失败的回调 + print("on create room fail: \(error.localizedDescription)") + button.isEnabled = true // 恢复按钮可用状态 + } +} +``` + +### 3. 加入房间 + +本节展示如何让房主加入语聊房。 + +#### 声明属性 + +在 `ViewController` 类中声明了一个可选属性,用于存储 `AUIVoiceChatRoomView` 实例以显示语聊房的详情界面。 + +```swift +class ViewController: UIViewController { + var voiceChatView: AUIVoiceChatRoomView? + ... +} +``` + +#### 创建房间详情页并启动房间 + +在 `ViewController` 中,创建一个 `AUIVoiceChatRoomView` 实例,并将它设置为 `voiceChatView` 属性,用于显示语聊房的详情界面。 + +调用 `VoiceChatUIKit` 类的 `launchRoom` 方法,并传入以下参数,以启动语聊房: + +- `roomInfo`:房间信息。 +- `voiceChatView`:房间的 UI View。 + + +```swift +func enterRoom(roomInfo: AUIRoomInfo) { + // 创建一个 AUIVoiceChatRoomView 实例,并设置其 frame 为当前视图的边界 + voiceChatView = AUIVoiceChatRoomView(frame: self.view.bounds) + + if let roomView = self.voiceChatView { + // 调用 VoiceChatUIKit 的 launchRoom 方法,启动房间 + VoiceChatUIKit.shared.launchRoom(roomInfo: roomInfo, + roomView: roomView) { [weak self] error in + guard let self = self else { return } + // 如果出现错误,直接返回 + if let _ = error { return } + + // 将 roomView 添加到当前视图中 + self.view.addSubview(roomView) + } + } +} +``` + +### 4. (可选)听众加入房间 + +本节展示如何让听众加入已存在的房间。 + +#### 添加按钮:加入房间 + +在 `ViewController` 类中,通过 iOS 原生方法增加一个「加入房间」按钮,用于让听众点击加入。 + +```swift +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) + + // highlight-start + // 作为听众加入房间的按钮 + 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) + // highlight-end +} +``` + + +#### 获取房间列表 + +在 `ViewController` 类中,调用 `VoiceChatUIKit` 类的 `getRoomInfoList` 方法并传入如下参数,以获取语聊房间列表: + +- `lastCreateTime`:房间列表的起始时间。 +- `pageSize`:房间列表的数量。 + +如果在房间列表中找到匹配的房间名,那么执行 `enterRoom` 函数。通过[此前步骤](//TODO)中已添加的 `enterRoom` 函数即可实现加入房间。 + +```swift +@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: nil, 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) +} +``` + +### 5. (可选)退出或销毁房间 + +本节展示如何退出并销毁房间。 + +在语聊房中,房主可以主动销毁房间,房间销毁时听众会被动退出房间。房间存在时,听众也可以主动退出房间。 + + +#### 主动退出或销毁房间 + +在 `enterRoom` 函数中添加如下高亮的几行代码,即可实现通过点击按钮主动退出房间(听众)或销毁房间(房主)。 + +```swift +func enterRoom(roomInfo: AUIRoomInfo) { + // 创建一个 AUIVoiceChatRoomView 实例,并设置其 frame 为当前视图的边界 + voiceChatView = AUIVoiceChatRoomView(frame: self.view.bounds) + + // highlight-start + // 点击退出房间按钮 + voiceChatView?.onClickOffButton = { [weak self] in + self?.destroyRoom(roomId: roomInfo.roomId) + } + // highlight-end + + if let roomView = self.voiceChatView { + // 调用 VoiceChatUIKit 的 launchRoom 方法,启动房间 + VoiceChatUIKit.shared.launchRoom(roomInfo: roomInfo, + roomView: roomView) { [weak self] error in + guard let self = self else { return } + // 如果出现错误,直接返回 + if let _ = error { return } + + // 将 roomView 添加到当前视图中 + self.view.addSubview(roomView) + } + } +} +``` + + +#### 被动退出房间 + +听众被动退出房间的代码逻辑如下: + +1. 在 `enterRoom` 函数中添加如下高亮的代码行。调用 `VoiceChatUIKit` 类的 `bindRespDelegate` 方法,订阅房间被销毁的回调。 + + ```swift + func enterRoom(roomInfo: AUIRoomInfo) { + // 创建一个 AUIVoiceChatRoomView 实例,并设置其 frame 为当前视图的边界 + voiceChatView = AUIVoiceChatRoomView(frame: self.view.bounds) + + // highlight-start + // 点击退出房间按钮 + voiceChatView?.onClickOffButton = { [weak self] in + self?.destroyRoom(roomId: roomInfo.roomId) + } + // highlight-end + + if let roomView = self.voiceChatView { + // 调用 VoiceChatUIKit 的 launchRoom 方法,启动房间 + VoiceChatUIKit.shared.launchRoom(roomInfo: roomInfo, + roomView: roomView) { [weak self] error in + guard let self = self else { return } + // 如果出现错误,直接返回 + if let _ = error { return } + // highlight-start + VoiceChatUIKit.shared.bindRespDelegate(delegate: self) + // highlight-end + // 将 roomView 添加到当前视图中 + self.view.addSubview(roomView) + } + } + } + ``` + +2. 在 `ViewController` 文件中,通过 `AUIRoomManagerRespDelegate` 中的 `onRoomDestroy` 回调来监听房间销毁事件。当监听到房间被销毁时,执行 `destroyRoom` 函数。 + + ```swift + extension ViewController: AUIRoomManagerRespDelegate { + // 房间销毁回调 + func onRoomDestroy(roomId: String) { + self.destroyRoom() + } + // 房间信息更新回调 + func onRoomInfoChange(roomId: String, roomInfo: AUIKitCore.AUIRoomInfo) { + } + // 房间成员列表更新回调 + func onRoomAnnouncementChange(roomId: String, announcement: String) { + } + // 听众被房主踢出房间回调 + func onRoomUserBeKicked(roomId: String, userId: String) { + self.destroyRoom() + } + } + ``` + +3. 在 `ViewController` 类中,增加 `destroyRoom` 函数。在该函数中,调用 `VoiceChatUIKit` 类的 `unbindRespDelegate` 方法,取消订阅房间被销毁的回调。 + + ```swift + func destroyRoom(roomId: String) { + // 在退出房间时取消订阅,调用 VoiceChatUIKit 的 unbindRespDelegate 方法 + VoiceChatUIKit.shared.unbindRespDelegate(delegate: self) + } + ``` + +#### 添加销毁逻辑 + +在 `destroyRoom` 函数中,添加如下高亮的几行代码。调用 `VoiceChatUIKit` 类的 `destoryRoom` 方法并传入房间 ID,以销毁房间。至此,`destroyRoom` 函数的代码补充完成。 + +```swift +func destroyRoom(roomId: String) { + // highlight-start + // 点击退出,调用 VoiceChatView 的 onBackAction 方法 + self.VoiceChatView?.onBackAction() + + // 将 VoiceChatView 从父视图中移除 + self.VoiceChatView?.removeFromSuperview() + + // 调用 VoiceChatUIKit 的 destoryRoom 方法销毁房间,传入房间 ID + VoiceChatUIKit.shared.destoryRoom(roomId: roomId) + // highlight-end + + // 在退出房间时取消订阅,调用 VoiceChatUIKit 的 unbindRespDelegate 方法 + VoiceChatUIKit.shared.unbindRespDelegate(delegate: self) +} +``` \ No newline at end of file diff --git a/markdown/chatroom/AUIVoiceRoom/uivoice_enable_service.md b/markdown/chatroom/AUIVoiceRoom/uivoice_enable_service.md new file mode 100644 index 00000000000..917e69346b2 --- /dev/null +++ b/markdown/chatroom/AUIVoiceRoom/uivoice_enable_service.md @@ -0,0 +1,18 @@ +//TODO +- 同时还需要开通以下服务 + - RTM, [联系客服人员开通](https://www.shengwang.cn) + - NCS, RTC频道事件回调通知, 处理人员进出/房间销毁逻辑 + - [开通消息通知服务 + ](https://docs.agora.io/cn/video-call-4.x/enable_webhook_ncs?platform=All%20Platforms) + - 选择以下事件类型 + - channel create, 101 + - channel destroy, 102 + - broadcaster join channel, 103 + - broadcaster leave channel, 104 + - 回调地址 + - https://你的域名/v1/ncs/callback + - 修改Secret + - 根据配置界面提供的Secret值, 修改项目配置文件application.yml的ncs.secret + - [频道事件回调 + ](https://docs.agora.io/cn/video-call-4.x/rtc_channel_event?platform=All%20Platforms) + - 环信 IM,在环信 Console 控制台进行配置 [开通配置环信即时通讯 IM 服务](https://docs-im-beta.easemob.com/document/server-side/enable_and_configure_IM.html) \ No newline at end of file diff --git a/markdown/chatroom/AUIVoiceRoom/uivoice_integration_android.md b/markdown/chatroom/AUIVoiceRoom/uivoice_integration_android.md new file mode 100644 index 00000000000..e872328d9e5 --- /dev/null +++ b/markdown/chatroom/AUIVoiceRoom/uivoice_integration_android.md @@ -0,0 +1,169 @@ +--- +title: 实现语聊房 +--- + +本文介绍如何集成语聊 UIKit 来实现房间的创建、用户进房、退房和销毁等功能。 + +## 示例项目 + +声网提供 [API-Examples-AUIKit/Android/VoiceRoomApp](https://github.com/AgoraIO-Community/API-Examples-AUIKit/tree/dev/voiceroom-android/Android/VoiceRoomApp) 示例项目供你参考本文提到的集成步骤和代码逻辑。 + + +## 准备开发环境 + +### 前提条件 + +- [Android Studio](https://developer.android.com/studio/) 3.5 及以上。本文以 [Android Studio Giraffe | 2022.3.1](https://developer.android.google.cn/studio/releases?hl=zh-cn) 和 JBR 17.0.6 为例。 +- Android 手机,版本 Android 5.0(API Level 21)及以上。 +- 可以访问互联网的计算机。确保你的网络环境没有部署防火墙,否则无法正常使用声网服务。 +- [开通服务](./enable-service)。 + + +### 创建项目 + +如需创建新项目,在 **Android Studio** 里,依次选择 **Phone and Tablet > Empty Activity**,创建 [Android 项目](https://developer.android.com/studio/projects/create-project)。 + + +创建项目后,Android Studio 会自动开始同步 gradle,稍等片刻至同步成功后再进行下一步操作。 + + +### 集成依赖 + +本节介绍如何集成语聊项目所需的依赖库: + +1. 下载声网提供的 [AUIVoiceRoom/Android/asceneskit](https://github.com/AgoraIO-Community/AUIVoiceRoom/tree/main/Android/asceneskit) 文件夹,并将该文件夹复制到与你的 `app` 文件夹所在的同一级目录下。 + +2. 在 `/setting.gradle.kts` 文件中添加如下行: + + ```kotlin + dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + // 添加 jitpack 仓库,拉取 auikit 库 + maven { url = java.net.URI.create("https://www.jitpack.io") } + } + } + + // 项目名需为你的 Android Studio 项目的真实名称 + rootProject.name = "VoiceRoomApp" + include(":app") + // 添加 asceneskit 库到项目里 + include(":asceneskit") + ``` + +3. 在 `/app/build.gradle.kts` 文件中添加如下行: + + ```kotlin + dependencies { + ... + + // 添加 asceneskit 依赖 + implementation(project(":asceneskit")) + } + ``` + +4. 点击 **Sync Now** 按钮,等待 gradle 同步完成。//TODO 图 + +### 配置权限 + +本节介绍如何配置项目所需权限: + +1. 在 `/app/Manifests/AndroidManifest.xml` 文件中添加如下行,配置网络、录音等权限: + + ```xml + + + + + + + + + + + ... + + + ``` + +2. 如果你的后端服务 host url 不是 https 开头,那么你需要在 `/app/Manifests/AndroidManifest.xml` 文件中添加如下高亮行://TODO 后端 link + + ```xml + + + + + + + + + + + + + + ... + + + + ... + + + ``` + +### 配置主题 + +在 `/app/src/main/res/values/themes.xml` 文件中添加如下行配置语聊房 UI 主题: + +```xml + + +