Write a ClubHouse

I believe that everyone has heard of ClubHouse, which is an invitation-oriented App featuring voice and social interaction. Led by leaders in various fields, it quickly went out of its way and became popular on social media around the world. However, it is only available for iOS and has been removed in China.

Last week I also tried to write a mock, from a technical point of view, the implementation of the voice interaction module, it is really simple, below is the renderings.

rendering

The flow chart

architecture

(Photo: Google)

Code implementation

Join channel

If the room is self-created, set the RTC user role to HOST, or “speaker,” before joining the channel. A speaker is a person whose voice is heard by everyone in the room. For the RTC SDK, the HOST identity means that as soon as you join a channel, you publish your own audio stream by default. The AUDIENCE subscribes only to the HOST’s audio stream.

/ / channel join RTC/RTM fun joinChannel () {RtcManager. Instance. JoinChannel (getToken (), getChannelId (), getSelfId (), if (isMeHost()) { Role.HOST } else { Role.AUDIENCE } ) RtmManager.instance.joinChannel(getChannelId()) }Copy the code

Raise your hand/cancel your hand

The RTM SDK is used to realize these two functions, and only a P2P message is sent to the host. One of the interesting things about the ClubHouse is that when I started doing it, I thought the host only had to say yes or no. But in fact, after receiving a raise of hands, the host cannot directly refuse or agree, but reversely invite the audience to raise their hands, and finally the audience decides whether to speak on stage or not.

This is actually the right thing to do for privacy.

{val json = JSONObject(). Apply {put("action", broadcastcmd.raise_hands) put("userName", broadcastcmd.raise_hands) getSelf()? .userName) put("avatar", getSelf()? .userIcon) } RtmManager.instance.sendPeerMessage(channelInfo.value? .hostId.toString(), json.toString()) updateUserStatusFromHttp(getSelfId(), {val json = JSONObject(). Apply {put("action", BroadcastCMD.CANCLE_RAISE_HANDS) } RtmManager.instance.sendPeerMessage(channelInfo.value? .hostId.toString(), json.toString()) }Copy the code

Invite to speak/agree/reject

Also, based on the RTM real-time signaling SDK, interaction between the audience and the host like this is made much easier. As long as both parties confirm the signaling, they can simply send a P2P message to achieve related functions.

Fun inviteLine(userId: 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())} {val json = JSONObject().apply {put("action", BroadcastCMD.ACCEPT_INVITE) } RtmManager.instance.sendPeerMessage(channelInfo.value? .hostId.toString(), json.toString()) }Copy the code

Change the identity

What should listeners do when they are invited to speak on stage by the host? For the RTC SDK, the HOST identity means that when you join a channel, you publish your own audio stream by default. The AUDIENCE subscribes only to the HOST’s audio stream. Therefore, we only need to change the AUDIENCE’s identity in THE RTC SDK, change the AUDIENCE into HOST, and then release their own audio stream, and their voice will be subscribed by all the AUDIENCE in the room.

// Click Agree to invite and change the RTC user role
channelVM.acceptLine()
channelVM.changeRoleToSpeaker()

// Change the role to speaker (HOST)
fun changeRoleToSpeaker(a) {
     RtcManager.instance.changeRoleToSpeaker()
}

Copy the code

Turn off the microphone

Because there may be multiple speakers at the same time, users will voluntarily turn off their microphones while someone is speaking. Avoid interfering with others. To turn off the microphone, simply call the muteLocalAudioStream method.

// Mute fun muteLocalAudio(mute:Boolean){rtcEngine? .let {it.muteLocalAudioStream(mute)} }Copy the code

Update the microphone icon locally

I have not noticed the second parameter of RecyclerView notifyItemChanged. When I wanted to update a View of an item, I used ViewHolder to find it. The notifyItemChanged second parameter implements 🤣

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)) } } } }Copy the code

The above are the general function points of a ClubHouse core speech module. Using some mature domestic audio and video SDK, it is very simple to achieve. For example, THE anyRTC RTC SDK I used does not limit the number of people in the room, the sound quality is very good, the SDK is relatively stable, and the monthly free 10,000 minutes ~

This project adopts the architecture recommended by Google as a whole, separating pages from data, using ViewModel and LiveData, and using DiffUtil optimization list and Kotlin coroutine to optimize asynchronous tasks.

Realized function

  1. Log in to get a room list
  2. Create public/private rooms
  3. Anchors publish audio/listeners subscribe to audio
  4. Raise your hand/cancel your hand
  5. The anchor invited the audience to the stage
  6. Raise your hand if you list

Download experience link

⏬ Download experience

🐱 making address

Third-party libraries

  • AnyRTC RTC AUDIO and video SDK
  • AnyRTC RTM real-time signaling SDK
  • RxHttp
  • BaseRecyclerViewAdapterHelper