Skip to main content
Version: 3.x.x

Change Mode - Android

In a live stream, audience members usually join in RECV_ONLY mode, meaning they can only view and listen to the hosts. However, if a host invites an audience member to actively participate (e.g., to speak or present), the audience member can switch their mode to SEND_AND_RECV using the changeMode() method.

caution

changeMode() only ever changes the mode of the participant who calls it. There is no form of changeMode() that takes another participant's id, and a host cannot change an audience member's mode directly.

Promotion therefore takes two pieces of code, one on each side, and neither does anything on its own:

  1. On the host — publish a request to the invited participant over PubSub. Step 1 and Step 2 below.
  2. On the invited participant — subscribe to that same topic and, in the message handler, call changeMode() yourself. Step 3 below. Without this subscriber the request reaches nobody and no mode ever changes.

An implementation that only publishes the request looks finished and does nothing.

This guide explains how to use the changeMode() method and walks through a sample implementation where a host invites an audience member to become a host using PubSub.

changeMode()​

  • The changeMode() method from the Room class switches the calling participant's own mode during a live stream — for example, an audience member becoming a host.
  • It takes only the target Mode. To move a different participant between modes, send them a PubSub request and let their client call changeMode() itself, as shown in the Implementation Guide below.
  • changeMode() is a suspend function, so call it from a coroutine. It throws a VideoSDKException with code 3054 (ERROR_ALREADY_IN_REQUESTED_STATE) if the participant is already in that mode, and 3055 (ERROR_OPERATION_IN_PROGRESS) while an earlier changeMode() call is still running. Neither is reported on onError.
  • After a change to SEND_AND_RECV, the mic and camera come back as they were just before the mode change: a device that was on turns back on, and one you had turned off stays off. For a participant who joined in RECV_ONLY or SIGNALLING_ONLY, micEnabled and camEnabled from createRoom() decide. Don't call unmuteMic() or enableCam() for a device that comes back on by itself: the call can throw 3054.

Example: a participant changing its own mode​

import live.videosdk.rtc.android.Room
import live.videosdk.rtc.android.VideoSDK
import live.videosdk.rtc.android.lib.Mode
import live.videosdk.rtc.android.lib.VideoSDKException

class LiveStreamActivity : AppCompatActivity() {
private lateinit var liveStream: Room

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_live_stream)
// create the live stream room
liveStream = VideoSDK.createRoom(...)

// join the live stream
lifecycleScope.launch {
try {
liveStream.join()
} catch (e: VideoSDKException) {
Log.e("VideoSDK", "join failed: ${e.code} ${e.name}")
}
}

// Button to change mode
val changeModeBtn: Button = findViewById(R.id.btnChangeMode)
changeModeBtn.setOnClickListener {
lifecycleScope.launch {
try {
liveStream.changeMode(Mode.SEND_AND_RECV)
} catch (e: VideoSDKException) {
// 3054: already in this mode, 3055: an earlier changeMode() is still running
Log.e("VideoSDK", "changeMode failed: ${e.code} ${e.name}")
}
}
}
}
}

Implementation Guide​

Step 1 : Create a Pubsub Topic​

  • Set up a PubSub topic to send a mode change request from the host to a specific audience member.
  • publish() is a suspend function, so sendInvite() is one too. It returns once the server has the message, and throws a VideoSDKException when the live stream is not joined or is reconnecting (3035 / 3065), or when the message could not be sent (4087).
suspend fun sendInvite(livestream: Room, participantId: String) {
val pubSubPublishOptions = PubSubPublishOptions(persist = false)

try {
livestream.pubSub.publish(
"REQUEST_TO_JOIN_AS_HOST_$participantId", // PubSub topic specific to participant
"SEND_AND_RECV", // message
pubSubPublishOptions
)
} catch (e: VideoSDKException) {
Log.e("VideoSDK", "publish failed: ${e.code} ${e.name}")
}
}

Step 2 : Create an Invite Button​

  • Add an "Invite on Stage" button for each audience member. When clicked, it publishes a PubSub message with the mode "SEND_AND_RECV" to that participant. sendInvite() suspends, so the adapter launches it on a CoroutineScope it was given, such as the activity's lifecycleScope.
// In ParticipantListAdapter.kt, inside showPopup method
if (participant!!.mode == Mode.RECV_ONLY) {
popup.menu.add("Add as a co-host")

popup.setOnMenuItemClickListener { item: MenuItem ->
if (item.toString() == "Add as a co-host") {
scope.launch { sendInvite(liveStream, participantId) }
holder.requestedIndicator.visibility = View.VISIBLE
holder.btnParticipantMoreOptions.isEnabled = false
return@setOnMenuItemClickListener true
}
false
}
}

Step 3 : Create a Listener to Change the Mode​

  • On the audience side, subscribe to the specific PubSub topic. When a mode request is received, update the participant’s mode using changeMode().
  • Subscribe from onRoomJoined(). subscribe() throws a VideoSDKException with code 3035 if the live stream is not joined yet, and liveStream.localParticipant.id is final only once the live stream is joined. subscribe() is a suspend function, so launch a coroutine for it.
// In your class where you define coHostListener
val coHostListener = object : PubSubMessageListener {
override fun onMessageReceived(message: PubSubMessage) {
showCoHostRequestDialog()
}
}

private val roomEventListener = object : RoomEventListener() {
override fun onRoomJoined(switchRoomId: String?) {
lifecycleScope.launch {
try {
liveStream.pubSub.subscribe(
"REQUEST_TO_JOIN_AS_HOST_${liveStream.localParticipant.id}",
coHostListener
)
} catch (e: VideoSDKException) {
Log.e("VideoSDK", "subscribe failed: ${e.code} ${e.name}")
}
}
}
}

// In the showCoHostRequestDialog method
private fun showCoHostRequestDialog() {
// Dialog setup code...

acceptBtn.setOnClickListener {
lifecycleScope.launch {
try {
liveStream.changeMode(Mode.SEND_AND_RECV)
} catch (e: VideoSDKException) {
Log.e("VideoSDK", "changeMode failed: ${e.code} ${e.name}")
}
}
}
// Rest of the dialog code...
}
note

onRoomJoined() also fires after a reconnect that needed a fresh join. Your subscription is kept, so subscribing again with the same listener throws 3073 (ERROR_INVALID_PARAMETER). The catch above handles it.

note

Calling changeMode() changes only the caller's mode. Every other client must rebuild its host and audience lists from the onParticipantModeChanged event, as described in Updating the UI on mode change.

Updating the UI on mode change​

changeMode() updates the mode of the participant who calls it. The room then delivers onParticipantModeChanged(participantId, mode) to every participant, including clients that did not initiate the change. Each client should use that event to update its UI.

In the event handler, rebuild the lists that drive your UI by filtering participants by their current mode, then refresh the view. Simply pinning, unpinning, or displaying a message is not enough: when a participant is promoted, their mode changes, but other clients will not automatically move them from the audience to the stage.

room.addEventListener(object : RoomEventListener() {
override fun onParticipantModeChanged(participantId: String, mode: Mode) {
// Rebuild both lists from the current participant state, then refresh.
speakers = participantsWithMode(Mode.SEND_AND_RECV)
viewers = participantsWithMode(Mode.RECV_ONLY)
notifyDataSetChanged()
}
})
caution

Rebuild both lists — the speakers and the viewers. Removing the participant from the audience list alone is the common half-fix: they disappear from the audience and never appear on stage.

API Reference​

The API references for all the methods and events utilized in this guide are provided below.

Got a Question? Ask us on discord