大家好,我是声网 RTE 开发者社区作者 @小曾同学。本次主要分享集成声网SDK实现语音聊天室。

01 前言

在日常生活中经常会看到一些聊天场景,比如在线KTV、连麦开黑、多人相亲、娱乐聊天室等应用场景,随着移动应用开发的需求不断增加,多人语音聊天室成为了一个热门的应用领域。那么聊天室该如何实现呢?你是想从0到1,还是集成第三方SDK呢?答案当然是集成第三方SDK,那么我们这篇文章就来教大家集成声网SDK实现一个语音聊天室Demo。

02 设计思路

  1. 在做产品之前需要明确需求,本次需求:实现语音聊天室Demo;

  2. 在确定需求之后,还需要对音视频这块有一定的了解,可以参考声网官网提供的音视频时序图,本次我们要实现的是多人语聊房,实现原理可以参考音视频的实现,音频通话不区分主播和观众,所有用户都是主播角色。

  1. 了解上述逻辑之后,设计Demo原型图,一个聊天室的构成基本上包含:输入房间名、加房、麦克风、用户界面等。我们本次主要实现一个简易聊天室demo,用户输入房间加入房间后,即可和远端用户保持通话,并可mute/unmute本地麦克风。设计如下

另外,在实现demo之前你需要一些准备工作,可参见【开发环境】。

03 开发环境

  • 开发平台:MacBook Pro
  • 编译工具:Xcode(14.2)
  • 真机:iPhone13(15.4.1)
  • Agora SDK:4.1.1

另外,你需要获取声网SDK、声网appID、Token等信息,具体获取方式可以参考官方文档。如果你还没有声网账号,可以通过这里免费注册,本次Demo使用的是SDK4.1.1版本,具体下载可查看SDK下载页面。

04 项目设置

1. 创建项目,集成声网SDK

项目名为:VoiceChatDemo,打开终端,进入根目录VoiceChatDemo下,输入命令pod init,该命令生成Podfile文件,并在Podfile文件中,输入pod 'AgoraRtcEngine_iOS','4.1.1’,表示集成声网sdk。之后在终端中输入命令pod install,表示下载依赖。

2. 添加媒体设备权限

本次实现的语聊房Demo,所以只需要给予麦克风权限

05 客户端实现

1. 加房页面创建

本次语音聊天室 Demo 主要涉及两个页面,一是用户加房页面 ViewController ,二是用户聊天室页面 RoomController 。而在RoomController中包含一个UICollection View,用于展示远端用户视图。

用户加房页面主要涉及5个内容,分别是 appidtokenchanneluid、加入房间。如果你还不知道如何获取声网appid等信息,可以参考官方文档。具体代码如下:

import UIKit
class ViewController: UIViewController {@IBOutlet weak var appidTF: UITextField!@IBOutlet weak var tokenTF: UITextField!@IBOutlet weak var uidTF: UITextField!@IBOutlet weak var roomTF: UITextField!override func viewDidLoad() {super.viewDidLoad()// Do any additional setup after loading the view.}
}

加房跳转逻辑在页面中设计,具体如下图

当用户点击加入房间button时,会自动跳转到用户聊天室页面,这种方式称为Segue,表示从一种场景转换到另外一种场景中。

2. 聊天室实现逻辑

RoomController.swift文件中,实现聊天室逻辑功能

2.1 初始化操作

1)导入Agora SDK

import AgoraRtcKit
//自 3.0.0 版本起,AgoraRtcEngineKit 类名更换为 AgoraRtcKit

2)初始化声网引擎

// 初始化AgoraRtcEngineKit,可加入自定义配置,比如加入频道是否开启麦克风、摄像头等。
let config = AgoraRtcEngineConfig()
config.appId = appid
config.channelProfile = profile
agoraKit = AgoraRtcEngineKit.sharedEngine(with: config, delegate: self)
agoraKit.enableAudio()
agoraKit.disableVideo()
agoraKit.enableAudioVolumeIndication(200, smooth: 3, reportVad: true)
//默认加入频道即发送音频,不发送视频
let option = AgoraRtcChannelMediaOptions()
option.publishCameraTrack = false
option.publishMicrophoneTrack = true
option.enableAudioRecordingOrPlayout = true
option.clientRoleType = .broadcaster
option.autoSubscribeAudio = true
2.2 聊天室页面设计

1)定义UserList

如下图,定义一个Collection View来渲染远端用户,名为User List,当远端用户加房时,UICollection View中就会增加一个自定义的Cell。

自定义的Cell的nib文件需要和RoomCell关联。

在使用Cell前,需要注册自定义的Cell到UICollection View中。

var nibName = UINib(nibName: "RoomCell", bundle:nil)
userList.register(nibName, forCellWithReuseIdentifier: "RoomCell")

2)数据绑定

那么,怎么把自定义的Cell 显示在Collection View里面,也就是说当远端用户加房时是怎么显示在页面上的?

Collection View 中有两个属性,一是dataSource,二是delegate其中,dataSource 表示数据来源,delegate 表示操作 Cell 的时候,一些事件委托谁来处理。

userList.delegate = self
userList.dataSource = self

那么dataSource是怎么绑定数据的呢?当远端用户加房后,怎么显示在界面上?

数据源是userArrayuserArray是远端用户的列表,当远端用户加入房间时会传入参数 uid,并将 uid 存到userArray数组中,当远端离开房间时,会调用remove()方法,将用户uid移除,在此过程中,控件需要重新刷新用户列表,即`userList.reloadData()``,将用户视图实时更新。

//远端用户加入房间
func rtcEngine(_ engine: AgoraRtcEngineKit, didJoinedOfUid uid: UInt, elapsed: Int) {let length = userArray.countif length == 0 {userArray.insert(Int(uid), at: 0)}else {userArray.insert(Int(uid), at: length-1 )}userList.reloadData()}
//远端用户离开房间
func rtcEngine(_ engine: AgoraRtcEngineKit, didOfflineOfUid uid: UInt, reason: AgoraUserOfflineReason) {var indexNum : Int = 0for (index,value) in userArray.enumerated() {if value == uid {indexNum = index}}userArray.remove(at: indexNum)userList.reloadData()}

另外,当用户加入房间后,用户的uid是怎么显示在界面上的呢?numberOfItemsInSection 表示区域内有多少个item(元素),也就是表示数组的个数,如果数组为5,那么就会返回5给collectionView;每一个cell 都可自己定义,有几个元素,那么第二个方法就会调用几次。而当用户进来时,显示用户uid,即 cell.uidLabel.text = “(userArray[indexPath.row])”

extension RoomController : UICollectionViewDataSource {func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {return userArray.count}func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "RoomCell", for: indexPath) as! RoomCellcell.uidLabel.text = "\(userArray[indexPath.row])"return cell}

3)mute/unmute实现

当用户点击开启麦克风按钮时,会调用muteLocalAudioStream(false) 方法,当点击关闭麦克风时,参数时 true。

//打开麦克风
@IBAction func openMic(_ sender: UIButton) {agoraKit.muteLocalAudioStream(false)
}
//关闭麦克风
@IBAction func closeMic(_ sender: UIButton) {agoraKit.muteLocalAudioStream(true)
}
2.3 完整代码如下

import UIKit
import AgoraRtcKit
class RoomController: UIViewController, UICollectionViewDelegate {// 初始化操作var isJoined : Bool = falsevar agoraKit : AgoraRtcEngineKit!var appid,token,roomid : String!var uid : Int32 = 0var profile:AgoraChannelProfile = .liveBroadcasting//用户列表var userArray = [Int]()@IBOutlet weak var userList: UICollectionView!//展示用户列表override func viewDidLoad() {super.viewDidLoad()self.setUp()}func setUp() {//初始化userList.delegate = selfuserList.dataSource = selfappid = "afe...7063"token = niluid = 0roomid = "zeng"let nibName = UINib(nibName: "RoomCell", bundle:nil)userList.register(nibName, forCellWithReuseIdentifier: "RoomCell")let config = AgoraRtcEngineConfig()config.appId = appidconfig.channelProfile = profileagoraKit = AgoraRtcEngineKit.sharedEngine(with: config, delegate: self)agoraKit.enableAudio()agoraKit.disableVideo()agoraKit.enableAudioVolumeIndication(200, smooth: 3, reportVad: true)let option = AgoraRtcChannelMediaOptions()option.publishCameraTrack = falseoption.publishMicrophoneTrack = trueoption.enableAudioRecordingOrPlayout = trueoption.clientRoleType = .broadcasteroption.autoSubscribeAudio = truelet result = agoraKit.joinChannel(byToken: token, channelId: roomid, uid: UInt(uid), mediaOptions: option)if result != 0 {self.showAlert(title: "Error", message: "joinChannel call failed: \(result), please check your params")} else {print("joinChannel successed!")self.showAlert(title: "Info", message: "joinChannel successed!")}}//打开麦克风@IBAction func openMic(_ sender: UIButton) {agoraKit.muteLocalAudioStream(false)}//关闭麦克风@IBAction func closeMic(_ sender: UIButton) {agoraKit.muteLocalAudioStream(true)}//显示用户列表界面@IBAction func showUserList(_ sender: Any) {}@IBAction func leaveRoom(_ sender: Any) {dismiss(animated: true)}override func viewDidDisappear(_ animated: Bool) {super.viewDidDisappear(animated)if isJoined {agoraKit.leaveChannel()}}func showAlert(title: String? = nil, message: String, textAlignment: NSTextAlignment = .center) {let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)let action = UIAlertAction(title: "OK", style: .cancel, handler: nil)alertController.addAction(action)if let messageLabel = alertController.view.value(forKeyPath: "_messageLabel") as? UILabel {messageLabel.textAlignment = textAlignment}self.present(alertController, animated: true, completion: nil)}
}
///AgoraRtcEngineDelegate
extension RoomController : AgoraRtcEngineDelegate {func rtcEngine(_ engine: AgoraRtcEngineKit, didOccur errorType: AgoraEncryptionErrorType) {self.showAlert(title: "Error", message: "didOccur: \(errorType), please check your params")}func rtcEngine(_ engine: AgoraRtcEngineKit, didJoinedOfUid uid: UInt, elapsed: Int) {
//        self.showAlert(title: "Info", message: "didJoinedOfUid: \(uid)")let length = userArray.countif length == 0 {userArray.insert(Int(uid), at: 0)}else {userArray.insert(Int(uid), at: length-1 )}userList.reloadData()}func rtcEngine(_ engine: AgoraRtcEngineKit, didJoinChannel channel: String, withUid uid: UInt, elapsed: Int) {self.isJoined = true}func rtcEngine(_ engine: AgoraRtcEngineKit, didOfflineOfUid uid: UInt, reason: AgoraUserOfflineReason) {var indexNum : Int = 0for (index,value) in userArray.enumerated() {if value == uid {indexNum = index}}userArray.remove(at: indexNum)userList.reloadData()}
}
///UITableViewDataSource && UITableViewDelegate
extension RoomController : UICollectionViewDataSource {func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {return userArray.count}func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "RoomCell", for: indexPath) as! RoomCellcell.uidLabel.text = "\(userArray[indexPath.row])"return cell}
}
}

06 Demo展示

07 小结

本次主要基于声网SDK实现iOS聊天室,功能比较简易,如果你想丰富自己的Demo,想要模拟一些场景,比如在线狼人杀、在线KTV、音效聊天室等,声网提供了非常丰富的API,会使语聊房更加沉浸、更加有趣、更加好听。主旨是打造一种“声临其境”的互动玩法,更多信息可参考声网的声动语聊。

————————

  • 欢迎注册声网帐号,领取每月 10000 分钟免费使用额度(注册流程参考)
  • 下载声网相关 SDK & Demo,体验四行代码、三十分钟快速构建沉浸式实时互动场景
  • 交流提问 & 撰写文章,更好的技术氛围可访问「声网 RTE 开发者社区」

开发最佳实践|集成声网 iOS SDK,实现语音聊天室相关推荐

  1. 基于声网 iOS SDK 实现视频直播应用

    视频互动直播是当前比较热门的玩法,我们经常见到有PK 连麦.直播答题.一起 KTV.电商直播.互动大班课.视频相亲等.本文将演示如何通过声网视频 SDK 在 iOS 端实现一个视频直播应用.话不多说, ...

  2. 基于声网 Flutter SDK 实现互动直播

    前言 互动直播是实现很多热门场景的基础,例如直播带货.秀场直播,还有类似抖音的直播 PK等.本文是由声网社区的开发者"小猿"撰写的Flutter基础教程系列中的第二篇,他将带着大家 ...

  3. 语音聊天app开发——语音聊天室系统如何开发

    网络直播行业近些年算得上是多元化发展,各个互联网平台陆续入驻,开发自身的短视频直播平台,像百度,腾讯,阿里等,直播也多种渠道发展,1对多视频直播,1对1直播,视频语音多人连麦直播,相比视频直播而言,语 ...

  4. iOS应用开发最佳实践

    <iOS应用开发最佳实践> 基本信息 作者: 王浩 出版社:电子工业出版社 ISBN:9787121207679 上架时间:2013-7-22 出版日期:2013 年8月 开本:16开 页 ...

  5. QCon北京2015:移动开发最佳实践专题前瞻

    从社交到游戏,从电商到O2O,移动互联网已经深入渗透到各行各业,而外卖和打车市场,更是正在经历着一些深刻的变化.巨额的融资和庞大的用户群当然是吸引眼球的,但是小团队背后的故事或许也能让你眼前一亮.不同 ...

  6. Android开发最佳实践

    原文链接:https://github.com/futurice/android-best-practices 转载来源:http://blog.csdn.net/asce1885/article/d ...

  7. 保姆级教程!基于声网 Web SDK实现音视频通话及屏幕共享

    前言 大家好,我是 @小曾同学,小伙伴们也可以叫我小曾- 如果你想实现一对一音视频通话和屏幕共享功能,不妨来看看这篇文章,保姆级教程,不需要从零实现,直接集成声网 SDK 即可轻松上手. 本文也分享了 ...

  8. Android开发最佳实践---Futurice之见

    原文链接:https://github.com/futurice/android-best-practices 本文是Futurice公司的Android开发人员总结的最佳实践,遵循这些准则可以避免重 ...

  9. 微信公众平台开发最佳实践

    <微信公众平台开发最佳实践>共分10章,案例程序采用广泛流行的PHP.MySQL.XML.CSS.JavaScript.HTML5等程序语言及数据库实现.系统完整地介绍微信公众平台基础接口 ...

最新文章

  1. 2022-2028年中国橡胶漆产业发展动态及未来趋势预测报告
  2. LeetCode 21. Merge Two Sorted Lists
  3. android运用 sqlite 实现简单的通讯录_大一新生作品:利用 C 语言实现quot;通讯录管理系统quot;,直言太简单...
  4. 【转】感知哈希算法——找出相似的图片
  5. 第一篇随笔,通常都是内容空洞的。
  6. HTML中属性值是否加引号规则详解
  7. 360手机卫士界面布局学习过程续(一)
  8. java菜鸟到cto 图_从菜鸟到入门,掌握 Log4j
  9. javascript练习----复选框全选,全不选,反选
  10. JavaScript基础---匿名函数
  11. 基于Promise对象的新一代Ajax API--fetch
  12. MySQL备份与恢复————用LVM快照恢复
  13. 华为专利全球第一:哪里跌倒,哪里爬起!
  14. 7. 敏捷软件开发框架 - 极限编程XP
  15. Emacs指北(做一个搬运工好累)
  16. 数字方法--按零补位
  17. 简述DB ,DBMS与DBS
  18. jstat命令查看jvm的GC情况
  19. C语言 信号集回调函数 避免子进程在信号回调注册完成之前全部结束
  20. css flex 文字右对齐,css flex align-items属性 交叉轴上对齐方式垂直对齐方式

热门文章

  1. Linux testdisk源码编译
  2. root用户给普通用户提权,禁止root远程登录
  3. 胡图工具Excel文件导出
  4. YYKit 框架-很厉害
  5. 计算机内存不足提示栻框,李传栻
  6. TightVNC实现Ubuntu远程虚拟桌面
  7. makedepend
  8. 实现类的顺序实例化(@DependOn)
  9. 这才是阅卷老师最喜欢的考研试卷!
  10. C语言的关键字restrict,C语言中restrict关键字学习