仿写一个 ClubHouse
相信 ClubHouse 大家都有所耳闻,就是一个主打语音社交,并且是邀请制的 App。在各个领域的大佬带动下,迅速出圈,火遍全球社交媒体。不过目前只有 iOS 版本,并且目前已在中国区下架。
上周咱也试着仿写了一个,从技术角度来说,实现它的语音互动模块,真的很简单,下面是效果图。
效果图
流程图
架构
(图:Google)
代码实现
加入频道
如果是自己创建的房间,则加入频道之前将 RTC 用户角色设置为 HOST ,即为“发言者”。所谓发言者,就是房间内的所有人都能听到 Ta 的声音的人。对于 RTC SDK 而言,HOST 身份就代表一加入频道,默认就会发布自己的音频流。AUDIENCE(听众) 则只订阅 HOST 的音频流。
//加入RTC/RTM 频道
fun joinChannel() {
RtcManager.instance.joinChannel(
getToken(), getChannelId(), getSelfId(), if (isMeHost()) {
Role.HOST
} else {
Role.AUDIENCE
}
)
RtmManager.instance.joinChannel(getChannelId())
}
复制代码
听众举手/取消举手
实现这两个功能需要用到 RTM SDK,只需要发送一条 P2P 消息给主持人即可。这里 ClubHouse 有个有意思的地方,一开始做的时候,我以为申请举手后,主持人只需同意或者拒绝就行。但实际上是主持人收到有人举手后,不能直接拒绝或者同意,而是反向邀请这个举手的观众,最终由观众决定到底要不要上台发言。
这样做其实对于隐私保护上是非常正确的。
//举手
fun raiseHand() {
val json = JSONObject().apply {
put("action", BroadcastCMD.RAISE_HANDS)
put("userName", getSelf()?.userName)
put("avatar", getSelf()?.userIcon)
}
RtmManager.instance.sendPeerMessage(channelInfo.value?.hostId.toString(), json.toString())
updateUserStatusFromHttp(getSelfId(), 1)
}
//取消举手
fun cancleRaiseHand() {
val json = JSONObject().apply {
put("action", BroadcastCMD.CANCLE_RAISE_HANDS)
}
RtmManager.instance.sendPeerMessage(channelInfo.value?.hostId.toString(), json.toString())
}
复制代码
邀请上台发言/同意/拒绝
同样的,基于 RTM 实时信令 SDK,像这样观众和主持人的互动实现变得格外的简单。只要双方确定好信令,简单的发送一条 P2P 消息即可实现相关功能。
//邀请上麦说话
fun inviteLine(userId: String) {
val json = JSONObject().apply {
put("action", BroadcastCMD.INVITE_SPEAK)
}
RtmManager.instance.sendPeerMessage(userId, json.toString())
}
//拒绝邀请
fun rejectLine() {
val json = JSONObject().apply {
put("action", BroadcastCMD.REJECT_INVITE)
put("userName", getSelf()?.userName)
}
RtmManager.instance.sendPeerMessage(channelInfo.value?.hostId.toString(), json.toString())
}
//同意邀请
fun acceptLine() {
val json = JSONObject().apply {
put("action", BroadcastCMD.ACCEPT_INVITE)
}
RtmManager.instance.sendPeerMessage(channelInfo.value?.hostId.toString(), json.toString())
}
复制代码
改变身份
当听众收到主持人邀请想上台发言时,该怎么做呢?文章开头有提到:对于 RTC SDK 而言,HOST 身份就代表一加入频道,默认就会发布自己的音频流。AUDIENCE(听众) 则只订阅 HOST 的音频流。所以我们只需要改变听众在 RTC SDK 中的身份,由 AUDIENCE 变成 HOST,即可发布自己的音频流,自己的声音将被房间内所有听众订阅到。
//点击同意邀请并改变 RTC 用户角色
channelVM.acceptLine()
channelVM.changeRoleToSpeaker()
// 将角色直至为说话者(HOST)
fun changeRoleToSpeaker() {
RtcManager.instance.changeRoleToSpeaker()
}
复制代码
关闭麦克风
因为同时可能由多个发言者,所以一般有人在发言时,用户都会主动关闭自己的麦克风。避免影响到他人发言。关闭麦克风,这里只需调用 muteLocalAudioStream 方法即可。
//本地麦克风静音
fun muteLocalAudio(mute:Boolean){
rtcEngine?.let {it.muteLocalAudioStream(mute)}
}
复制代码
局部更新麦克风图标
一直没注意过 RecyclerView notifyItemChanged 方法的第二个参数,之前想更新 item 的某一个 View 的时候都是通过 ViewHolder 去找,原来 notifyItemChanged 第二个参数就可以实现 🤣
speakerAdapter.notifyItemChanged(index, SpeakerPayload.AUDIO(it))
override fun convert(holder: BaseViewHolder, item: Speaker, payloads: List<Any>) {
super.convert(holder, item, payloads)
if (payloads.isNullOrEmpty()){
convert(holder,item)
return
}
payloads.forEach {
when(val payload = it as SpeakerPayload){
SpeakerPayload.AUDIO ->{
holder.setVisible(R.id.muted,!(payload.data as Boolean))
}
}
}
}
复制代码
以上,就是实现一个 ClubHouse 核心语音模块大致的功能点。利用国内一些成熟的音视频 SDK ,实现起来非常简单。比如我所采用的 anyRTC RTC SDK,不限制房间内上麦人数,音质也很好,SDK 也比较稳定,每月还免费送一万分钟~
这个项目整体采用谷歌推荐的架构,页面与数据分离,用到了 ViewModel、LiveData,并且使用 DiffUtil 优化列表,Kotlin 协程优化异步任务等。
已实现功能
登录获取房间列表
创建公开/私密类型房间
主播发布音频/听众订阅音频
听众举手/取消举手
主播邀请听众上台
举手列表
下载体验链接
⏬ 下载体验
🐱 github地址
第三方库
评论