大数跨境
0
0

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

开发最佳实践|集成声网 iOS SDK,实现语音聊天室 RTE开发者社区
2023-05-23
1
导读:手把手教你实现一个语音聊天室
大家好,我是声网 RTE 开发者社区作者 @小曾同学。本次主要分享集成声网SDK实现语音聊天室。
01

 前言   ‍‍‍‍‍‍‍‍‍

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

  设计思路 

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

2. 在确定需求之后,还需要对音视频这块有一定的了解,可以参考声网官网提供的[音视频时序图](https://docs.agora.io/cn/video-call-4.x/start_call_ios_ng?platform=iOS#othermethods),本次我们要实现的是多人语聊房,实现原理可以参考音视频的实现,音频通话不区分主播和观众,所有用户都是主播角色。

3. 了解上述逻辑之后,设计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 UIKitclass 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 = 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 = 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 = selfuserList.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 UIKitimport AgoraRtcKitclass 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)    }}///AgoraRtcEngineDelegateextension 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 && UITableViewDelegateextension 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,会使语聊房更加沉浸、更加有趣、更加好听。主旨是打造一种“声临其境”的互动玩法,更多信息可参考声网的声动语聊

 

READING
参考资料

●音视频时序图
https://docportal.shengwang.cn/cn/video-call-4.x/start_call_ios_ng?platform=iOS#othermethods
 官方文档

https://docportal.shengwang.cn/cn/Agora+Platform/get_appid_token?utm_medium=referral&utm_campaign=XZ03

 ● 免费注册

https://sso2.agora.io/cn/v4/signup/with-sms?utm_medium=referral&utm_campaign=XZ03
● SDK下载页面
https://docportal.shengwang.cn/cn/All/downloads?utm_source=AgoraWeChat_company&utm_medium=referral&utm_campaign=XZ01&_gl=1%2a1h8kl29%2a_ga%2aMTMyMzkzMzI2NS4xNjc4MDk1NzEx%2a_ga_BFVGG7E02W%2aMTY3ODc4OTAxOC42LjEuMTY3ODc5MDA1MC4wLjAuMA..&platform=iOS

● 官方文档


https://docportal.shengwang.cn/cn/Agora+Platform/get_appid_token?utm_medium=referral&utm_campaign=XZ03
● 声动语聊
 https://www.shengwang.cn/solution/voicechat/




关注「声网开发者」

关注实时互动领域的

技术实践行业洞察人物观点


☟☟

【声明】内容源于网络
0
0
RTE开发者社区
RTE 开发者社区是聚焦实时互动领域的中立开发者社区。不止于纯粹的技术交流,我们相信开发者具备更加丰盈的个体价值。行业发展变革、开发者职涯发展、技术创业创新资源,我们将陪跑开发者,共享、共建、共成长。
内容 1122
粉丝 0
RTE开发者社区 RTE 开发者社区是聚焦实时互动领域的中立开发者社区。不止于纯粹的技术交流,我们相信开发者具备更加丰盈的个体价值。行业发展变革、开发者职涯发展、技术创业创新资源,我们将陪跑开发者,共享、共建、共成长。
总阅读197
粉丝0
内容1.1k