开发最佳实践|集成声网 iOS SDK,实现语音聊天室
大家好,我是声网 RTE 开发者社区作者 @小曾同学。本次主要分享集成声网SDK实现语音聊天室。
01 前言
在日常生活中经常会看到一些聊天场景,比如在线KTV、连麦开黑、多人相亲、娱乐聊天室等应用场景,随着移动应用开发的需求不断增加,多人语音聊天室成为了一个热门的应用领域。那么聊天室该如何实现呢?你是想从0到1,还是集成第三方SDK呢?答案当然是集成第三方SDK,那么我们这篇文章就来教大家集成声网SDK实现一个语音聊天室Demo。
02 设计思路
-
在做产品之前需要明确需求,本次需求:实现语音聊天室Demo;
-
在确定需求之后,还需要对音视频这块有一定的了解,可以参考声网官网提供的音视频时序图,本次我们要实现的是多人语聊房,实现原理可以参考音视频的实现,音频通话不区分主播和观众,所有用户都是主播角色。
- 了解上述逻辑之后,设计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个内容,分别是 appid
、token
、channel
、uid
、加入房间。如果你还不知道如何获取声网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
是怎么绑定数据的呢?当远端用户加房后,怎么显示在界面上?
数据源是userArray
,userArray
是远端用户的列表,当远端用户加入房间时会传入参数 uid,并将 uid 存到userArray
数组中,当远端离开房间时,会调用remove()
方法,将用户uid移除,在此过程中,控件需要重新刷新用户列表,即`userList.reloadData()``,将用户视图实时更新。
//远端用户加入房间 func rtcEngine(_ engine: AgoraRtcEngineKit, didJoinedOfUid uid: UInt, elapsed: Int) { let length = userArray.count if 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 = 0 for (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! RoomCell cell.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 = false var agoraKit : AgoraRtcEngineKit! var appid,token,roomid : String! var uid : Int32 = 0 var profile:AgoraChannelProfile = .liveBroadcasting //用户列表 var userArray = [Int]() @IBOutlet weak var userList: UICollectionView! //展示用户列表 override func viewDidLoad() { super.viewDidLoad() self.setUp() } func setUp() { //初始化 userList.delegate = self userList.dataSource = self appid = "afe...7063" token = nil uid = 0 roomid = "zeng" let nibName = UINib(nibName: "RoomCell", bundle:nil) userList.register(nibName, forCellWithReuseIdentifier: "RoomCell") 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 let 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.count if 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 = 0 for (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! RoomCell cell.uidLabel.text = "\(userArray[indexPath.row])" return cell } } }
06 Demo展示
07 小结
本次主要基于声网SDK实现iOS聊天室,功能比较简易,如果你想丰富自己的Demo,想要模拟一些场景,比如在线狼人杀、在线KTV、音效聊天室等,声网提供了非常丰富的API,会使语聊房更加沉浸、更加有趣、更加好听。主旨是打造一种“声临其境”的互动玩法,更多信息可参考声网的声动语聊。
- 欢迎注册声网帐号,领取每月 10000 分钟免费使用额度(注册流程参考)
- 下载声网相关 SDK & Demo,体验四行代码、三十分钟快速构建沉浸式实时互动场景
- 交流提问 & 撰写文章,更好的技术氛围可访问「声网 RTE 开发者社区」

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Velocity不用愁!Velocity系统的前端工程化之路 | 京东云技术团队
Velocity是一个基于Java的Web页面模版引擎。十多年前,Velocity将Java代码从Web页面中分离出来,使得开发者能够并行网页开发和Java开发。随着十年前后端分离的浪潮涌动,回首再面对这些基于Velocity的旧系统,无论是后端还是前端人员维护,都会存在诸多问题: (1)后端人员维护:不熟悉前端开发模式,需要花费大量精力学习UI和Js框架; (2)前端人员维护:Velocity渲染依赖Java环境,需要花费大量精力学习Maven工程、环境配置,且前端MVC框架版本老,开发效率低。 这种情况广泛存在于零售内部的一些旧系统中,且业务需求的不断迭代,会导致系统维护成本越来越高。针对需要频繁迭代的页面模块,常见的应对措施是采用前后端分离方案,对页面进行整体重构,但如此以后,整体重构的代价是巨大的,主要体现在如下几点: (1)需要覆盖所有的业务场景和需求; (2)缺乏覆盖全场景的测试物料; (3)页面重构本身不带来业务价值。 我们团队承担了B端业务,由于业务启动时间早,也不可避免需要维护这些Velocity系统。 针对老系统维护难的痛点,我们在实践中探索了一种对老系统侵入程度低...
- 下一篇
Apache Hudi 在袋鼠云数据湖平台的设计与实践
在大数据处理中,实时数据分析是一个重要的需求。随着数据量的不断增长,对于实时分析的挑战也在不断加大,传统的批处理方式已经不能满足实时数据处理的需求,需要一种更加高效的技术来解决这个问题。Apache Hudi(Hadoop Upserts Deletes and Incremental Processing)就是这样一种技术,提供了高效的实时数据仓库管理功能。 本文将介绍袋鼠云基于 Hudi 构建数据湖的整体方案架构及其在实时数据仓库处理方面的特点,并且为大家展示一个使用 Apache Hudi 的简单示例,便于新手上路。 Apache Hudi 介绍 Apache Hudi 是一个开源的数据湖存储系统,可以在 Hadoop 生态系统中提供实时数据仓库处理功能。Hudi 最早由 Uber 开发,后来成为 Apache 顶级项目。 Hudi 主要特性 · 支持快速插入和更新操作,以便在数据仓库中实时处理数据; · 提供增量查询功能,可有效提高数据分析效率; · 支持时间点查询,以便查看数据在某一时刻的状态; · 与 Apache Spark、Hive 等大数据分析工具兼容。 Hudi 架...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2整合Redis,开启缓存,提高访问速度
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- MySQL8.0.19开启GTID主从同步CentOS8
- Mario游戏-低调大师作品
- Linux系统CentOS6、CentOS7手动修改IP地址
- Docker安装Oracle12C,快速搭建Oracle学习环境
- CentOS7安装Docker,走上虚拟化容器引擎之路