阅读 310

anyHouse - Android 仿写 ClubHouse

仿写一个 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 协程优化异步任务等。

已实现功能

  1. 登录获取房间列表
  2. 创建公开/私密类型房间
  3. 主播发布音频/听众订阅音频
  4. 听众举手/取消举手
  5. 主播邀请听众上台
  6. 举手列表

下载体验链接

下载体验

🐱 github地址

第三方库

文章分类
Android
文章标签