Invite Guest on Stage - Android
In this guide, we will see how you can request a viewer to join your livestream by using the changeMode()
.
Important Changes Android SDK in Version v0.2.0
- The following modes have been deprecated:
CONFERENCE
has been replaced bySEND_AND_RECV
VIEWER
has been replaced bySIGNALLING_ONLY
Please update your implementation to use the new modes.
⚠️ Compatibility Notice:
To ensure a seamless meeting experience, all participants must use the same SDK version.
Do not mix version v0.2.0 + with older versions, as it may cause significant conflicts.
Let's first have a look at how we will be using the PubSub
mechanism to acheive the requesting and switching of the participant's mode.
Before going forward in this guide, do make sure all the attendees join the meeting with mode as SIGNALLING_ONLY
and the host joins with mode as SEND_AND_RECV
Step 1: Loading Viewer List
The host will see a list of participants who have joined as SIGNALLING_ONLY
along with a button that, when selected, will invite that user to join the livestream.
Here we are using RecyclerView
to display viewer list with button.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#2B3034"
android:id="@+id/layout_participants">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:paddingVertical="15sp"
android:text="Viewer List"
android:textSize="20sp"
android:textColor="@color/white"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvViewersLinearView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="15sp" />
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:gravity="center"
android:orientation="horizontal"
android:padding="12dp">
<TextView
android:id="@+id/viewerName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="20sp"
android:textColor="@color/white"/>
<Button
android:id="@+id/btnRequest"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Request to join Stage"
android:textAllCaps="false" />
</LinearLayout>
- Create a recycler view adapter named
ViewerListAdapter
which will show the viewer list. CreatePeerViewHolder
in the adapter which will extendRecyclerView.ViewHolder
.
- Kotlin
- Java
class ViewerListAdapter(items: ArrayList<Participant?>?, private val meeting: Meeting) :
RecyclerView.Adapter<ViewerListAdapter.ViewHolder?>() {
private var viewers = ArrayList<Participant?>()
init {
viewers.addAll(items!!)
// adding Meeting Event listener to get the participant join/leave and panticipant mode change event in the meeting.
meeting.addEventListener(object : MeetingEventListener() {
override fun onParticipantJoined(participant: Participant) {
super.onParticipantJoined(participant)
viewers = ArrayList()
viewers.addAll(allViewers)
notifyDataSetChanged()
}
override fun onParticipantLeft(participant: Participant) {
super.onParticipantLeft(participant)
viewers = ArrayList()
viewers.addAll(allViewers)
notifyDataSetChanged()
}
override fun onParticipantModeChanged(data: JSONObject) {
viewers = ArrayList()
viewers.addAll(allViewers)
notifyDataSetChanged()
}
})
}
private val allViewers: ArrayList<Participant?>
private get() {
val viewerList: ArrayList<Participant?> = ArrayList()
val participants: Iterator<Participant> = meeting.participants.values.iterator()
for (i in 0 until meeting.participants.size) {
val participant = participants.next()
if (participant.mode == "SIGNALLING_ONLY") {
viewerList.add(participant)
}
}
return viewerList
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
LayoutInflater.from(parent.context)
.inflate(R.layout.item_viewer_list_layout, parent, false)
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val participant = viewers[position]
holder.viewerName.text = viewers[position]!!.displayName
}
override fun getItemCount(): Int {
return viewers.size
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var viewerName: TextView
var btnRequest: Button
init {
viewerName = itemView.findViewById(R.id.viewerName)
btnRequest = itemView.findViewById(R.id.btnRequest)
}
}
}
public class ViewerListAdapter extends RecyclerView.Adapter<ViewerListAdapter.ViewHolder> {
private ArrayList<Participant> viewers = new ArrayList<>();
private Meeting meeting;
public ViewerListAdapter(ArrayList<Participant> items, Meeting meeting) {
this.meeting = meeting;
viewers.addAll(items);
// adding Meeting Event listener to get the participant join/leave and panticipant mode change event in the meeting.
meeting.addEventListener(new MeetingEventListener() {
@Override
public void onParticipantJoined(Participant participant) {
super.onParticipantJoined(participant);
viewers = new ArrayList<>();
viewers.addAll(getAllViewers());
notifyDataSetChanged();
}
@Override
public void onParticipantLeft(Participant participant) {
super.onParticipantLeft(participant);
viewers = new ArrayList<>();
viewers.addAll(getAllViewers());
notifyDataSetChanged();
}
@Override
public void onParticipantModeChanged(JSONObject data) {
viewers = new ArrayList<>();
viewers.addAll(getAllViewers());
notifyDataSetChanged();
}
});
}
private ArrayList<Participant> getAllViewers() {
ArrayList<Participant> viewerList = new ArrayList();
Iterator<Participant> participants = meeting.getParticipants().values().iterator();
for (int i = 0; i < meeting.getParticipants().size(); i++) {
final Participant participant = participants.next();
if (participant.getMode().equals("SIGNALLING_ONLY")) {
viewerList.add(participant);
}
}
return viewerList;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_viewer_list_layout, parent, false));
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
final Participant participant = viewers.get(position);
holder.viewerName.setText(viewers.get(position).getDisplayName());
}
@Override
public int getItemCount() {
return viewers.size();
}
static class ViewHolder extends RecyclerView.ViewHolder {
TextView viewerName;
Button btnRequest;
public ViewHolder(@NonNull View itemView) {
super(itemView);
viewerName = itemView.findViewById(R.id.viewerName);
btnRequest = itemView.findViewById(R.id.btnRequest);
}
}
}
- Set this adapter to
RecyclerView
- Kotlin
- Java
btnViewerList.setOnClickListener { openViewerList() }
fun openViewerList() {
val viewerListView: RecyclerView
val bottomSheetDialog = BottomSheetDialog(mContext!!)
val layout_participants = findViewById(R.id.layout_participants)
val v3 = LayoutInflater.from(mContext)
.inflate(R.layout.layout_viewers_list_view, layout_participants)
bottomSheetDialog.setContentView(v3)
viewerListView = v3.findViewById(R.id.rvViewersLinearView)
viewerListView.minimumHeight = 400
bottomSheetDialog.show()
val viewerList: ArrayList<*> = ArrayList<Any?>()
val participants: Iterator<Participant> = meeting!!.participants.values.iterator()
for (i in 0 until meeting!!.participants.size) {
val participant = participants.next()
if (participant.mode == "SIGNALLING_ONLY")
viewerList.add(participant)
}
viewerListView.layoutManager = LinearLayoutManager(mContext)
viewerListView.adapter = ViewerListAdapter(viewerList, meeting!!)
viewerListView.setHasFixedSize(true)
}
btnViewerList.setOnClickListener(v -> openParticipantList());
public void openViewerList() {
RecyclerView viewerListView;
BottomSheetDialog bottomSheetDialog = new BottomSheetDialog(mContext);
LinearLayout layout_participants = findViewById(R.id.layout_participants);
View v3 = LayoutInflater.from(mContext).inflate(R.layout.layout_viewers_list_view, layout_participants);
bottomSheetDialog.setContentView(v3);
viewerListView = v3.findViewById(R.id.rvViewersLinearView);
viewerListView.setMinimumHeight(400);
bottomSheetDialog.show();
ArrayList viewerList = new ArrayList();
final Iterator<Participant> participants = meeting.getParticipants().values().iterator();
for (int i = 0; i < meeting.getParticipants().size(); i++) {
final Participant participant = participants.next();
if (participant.getMode().equals("SIGNALLING_ONLY"))
viewerList.add(participant);
}
viewerListView.setLayoutManager(new LinearLayoutManager(mContext));
viewerListView.setAdapter(new ViewerListAdapter(viewerList, meeting));
viewerListView.setHasFixedSize(true);
}

Step 2: Requesting a Viewer to Join Livestream
We have a Viewer list ready. Now, let us handle the click event for the btnRequest
.
We will be using CHANGE_MODE_ + participantId
as the topic for PubSub.
- Kotlin
- Java
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
//...
holder.btnRequest.setOnClickListener { v: View? ->
val pubSubPublishOptions = PubSubPublishOptions()
pubSubPublishOptions.isPersist = false
meeting.pubSub.publish(
"CHANGE_MODE_" + participant!!.id,
participant.id,
pubSubPublishOptions
)
}
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
//...
holder.btnRequest.setOnClickListener(v -> {
PubSubPublishOptions pubSubPublishOptions = new PubSubPublishOptions();
pubSubPublishOptions.setPersist(false);
//highloght-next-line
meeting.pubSub.publish("CHANGE_MODE_"+ participant.getId(), participant.getId(), pubSubPublishOptions);
});
}
Step 3: Showing Viewer Request Dialog
After implementing the Host requesting viewer to join the livestream, let's display the viewer's request dialogue and switch the SIGNALLING_ONLY
mode to SEND_AND_RECV
.
- Kotlin
- Java
meeting!!.addEventListener(object : MeetingEventListener() {
override fun onMeetingJoined() {
val changeModeListener =
PubSubMessageListener { pubSubMessage: PubSubMessage ->
if (pubSubMessage.message == meeting!!.localParticipant.id) {
showRequestDialog(pubSubMessage.senderName)
}
}
meeting!!.pubSub.subscribe("CHANGE_MODE_" + meeting!!.localParticipant.id, changeModeListener)
}
})
private fun showRequestDialog(name: String) {
val builder = AlertDialog.Builder(
mContext!!
)
builder.setMessage("$name has request you to go on stage")
builder.setTitle("Change mode Request")
builder.setCancelable(false)
builder.setPositiveButton("Yes") { dialog: DialogInterface, _: Int ->
meeting!!.changeMode("SEND_AND_RECV")
//...
dialog.dismiss()
}
builder.setNegativeButton(
"No"
) { dialog: DialogInterface, which: Int -> dialog.dismiss() }
val alertDialog = builder.create()
alertDialog.show()
}
meeting.addEventListener(new MeetingEventListener() {
@Override
public void onMeetingJoined() {
PubSubMessageListener changeModeListener = pubSubMessage -> {
if (pubSubMessage.getMessage().equals(meeting.getLocalParticipant().getId())) {
showRequestDialog(pubSubMessage.getSenderName());
}
};
meeting.pubSub.subscribe("CHANGE_MODE_"+ meeting.getLocalParticipant().getId(), changeModeListener);
}
}
private void showRequestDialog(String name) {
AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
builder.setMessage(name + " has request you to go on stage");
builder.setTitle("Change mode Request");
builder.setCancelable(false);
builder.setPositiveButton("Yes", (dialog, which) -> {
meeting.changeMode("SEND_AND_RECV");
//...
dialog.dismiss();
});
builder.setNegativeButton("No", (dialog, which) -> {
dialog.dismiss();
});
AlertDialog alertDialog = builder.create();
alertDialog.show();
}

Step 4: Pin the participant
We need to pin the participant so that he/she can appears on the livestream. To achieve this, we will listen to the callback on the onParticipantModeChanged
of Meeting
class.
- Kotlin
- Java
class ParticipantListAdapter(
items: ArrayList<Participant?>?,
private val meeting: Meeting,
private val context: Context
) : RecyclerView.Adapter<ParticipantListAdapter.ViewHolder>() {
// ...
init {
//...
meeting.addEventListener(object : MeetingEventListener() {
override fun onParticipantJoined(participant: Participant) {
//...
}
override fun onParticipantLeft(participant: Participant) {
// ...
}
override fun onParticipantModeChanged(data: JSONObject) {
//...
val participant = meeting.participants[data.getString("participantId")]
if (data.getString("mode") == "SEND_AND_RECV") {
participant!!.pin("SHARE_AND_CAM")
} else {
participant!!.unpin("SHARE_AND_CAM")
}
}
})
}
//...
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
//...
}
}
public class ParticipantListAdapter extends RecyclerView.Adapter<ParticipantListAdapter.ViewHolder> {
// ...
public ParticipantListAdapter(ArrayList<Participant> items, Meeting meeting, Context context) {
//...
meeting.addEventListener(new MeetingEventListener() {
@Override
public void onParticipantJoined(Participant participant) {
//...
}
@Override
public void onParticipantLeft(Participant participant) {
//...
}
@Override
public void onParticipantModeChanged(JSONObject data) {
//...
Participant participant = meeting.getParticipants().get(data.getString("participantId"));
if (data.getString("mode").equals("SEND_AND_RECV"))
{
participant.pin("SHARE_AND_CAM");
}else
{
participant.unpin("SHARE_AND_CAM");
}
}
});
}
//...
static class ViewHolder extends RecyclerView.ViewHolder {
//...
}
}
API Reference
The API references for all the methods utilized in this guide are provided below.
Got a Question? Ask us on discord