Skip to main content
Version: 0.x.x

Rendering Host and Audience Views - Android

In a live stream setup, only hosts (participants in SEND_AND_RECV mode) can broadcast their audio and video. Audience members (in RECV_ONLY mode) are passive viewers who do not share their audio/video.

To ensure optimal performance and a clean user experience, your app should:

  • Render audio and video elements only for hosts (i.e., participants in SEND_AND_RECV mode).
  • Display the total audience count to give context on viewership without rendering individual audience tiles.

Filtering and Rendering Hosts​

The steps involved in rendering the audio and video of hosts are as follows.

  1. Filtering Hosts and Checking their Mic/Webcam Status
  2. Rendering Video Streams of Hosts
  3. Rendering Audio Streams of Hosts

1. Filtering Hosts and Checking their Mic/Webcam Status ​

In a live stream, only participants in SEND_AND_RECV mode (i.e., hosts) actively share their audio and video. To render their streams, begin by accessing all participants using the Meeting class. Then, filter out only those in SEND_AND_RECV mode.

private fun showParticipants() {
// Clear existing lists
speakerList?.clear()

// Filter for host participants (SEND_AND_RECV mode)
liveStream?.participants?.values?.forEach { participant ->
if (participant.mode == "SEND_AND_RECV" && speakerList!!.size < 4) {
speakerList?.add(participant)
}
}

// Add local participant if they're a host
if (liveStream?.localParticipant?.mode == "SEND_AND_RECV" && !speakerList!!.contains(liveStream?.localParticipant)) {
speakerList?.add(0, liveStream?.localParticipant!!)
}

// Update the UI with filtered participants
updateSpeakerGrid()
}

private fun updateSpeakerGrid() {
// Update UI based on number of speakers
when (speakerList?.size) {
1 -> {
speakerOneContainer?.visibility = View.VISIBLE
speakerTwoContainer?.visibility = View.GONE
speakerThreeContainer?.visibility = View.GONE
speakerFourContainer?.visibility = View.GONE

// Show first speaker
showInGUI(speakerList!![0], speakerOneView, speakerOneNameText)
}
2 -> {
speakerOneContainer?.visibility = View.VISIBLE
speakerTwoContainer?.visibility = View.VISIBLE
speakerThreeContainer?.visibility = View.GONE
speakerFourContainer?.visibility = View.GONE

// Show first and second speakers
showInGUI(speakerList!![0], speakerOneView, speakerOneNameText)
showInGUI(speakerList!![1], speakerTwoView, speakerTwoNameText)
}
}
}

2. Rendering Video Streams of Hosts​

Once you've filtered for participants in SEND_AND_RECV mode (i.e., hosts), you can use the Participant class to access their real-time data, including their webcamStream, webcamOn, and whether they are the local participant.

For each of these participants, use the Participant , which provides real-time information like displayName, micOn, and webcamOn. Display their name along with the current status of their microphone and webcam. If the webcam is off, show a simple placeholder with their name. If it's on, render their video feed. This ensures only hosts are visible to the audience, keeping the experience clean and intentional.

private fun showInGUI(participant: Participant, videoView: VideoView, nameText: TextView) {
// Set participant name
nameText.text = if (participant.id == liveStream?.localParticipant?.id) "You" else participant.displayName

// Get references to status indicators
val micStatusIcon = videoView.findViewById<ImageView>(R.id.ivMicStatus)
val micStatusText = videoView.findViewById<TextView>(R.id.tvMicStatus)
val webcamStatusIcon = videoView.findViewById<ImageView>(R.id.ivWebcamStatus)
val webcamStatusText = videoView.findViewById<TextView>(R.id.tvWebcamStatus)

// Set initial status
updateMicStatus(participant.isMicOn, micStatusIcon, micStatusText)
updateWebcamStatus(participant.isWebcamOn, webcamStatusIcon, webcamStatusText, videoView)

// Add participant listener for status changes
participant.addEventListener(object : ParticipantEventListener() {
override fun onStreamEnabled(stream: Stream) {
activity?.runOnUiThread {
if (stream.kind.equals("video", ignoreCase = true)) {
// Update webcam status
updateWebcamStatus(true, webcamStatusIcon, webcamStatusText, videoView)

// Set up video stream
videoView.visibility = View.VISIBLE
videoView.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL)

// Mirror video for local participant
if (participant.id == liveStream?.localParticipant?.id) {
videoView.setMirror(true)
}

videoView.addTrack(stream.mediaStream, stream.trackId)
} else if (stream.kind.equals("audio", ignoreCase = true)) {
// Update mic status
updateMicStatus(true, micStatusIcon, micStatusText)
}
}
}

override fun onStreamDisabled(stream: Stream) {
activity?.runOnUiThread {
if (stream.kind.equals("video", ignoreCase = true)) {
// Update webcam status
updateWebcamStatus(false, webcamStatusIcon, webcamStatusText, videoView)

// Hide video view
videoView.visibility = View.GONE
} else if (stream.kind.equals("audio", ignoreCase = true)) {
// Update mic status
updateMicStatus(false, micStatusIcon, micStatusText)
}
}
}
})

// Set up initial streams
participant.streams.values.forEach { stream ->
if (stream.kind.equals("video", ignoreCase = true)) {
// Set up video stream
videoView.visibility = View.VISIBLE
videoView.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL)

// Mirror video for local participant
if (participant.id == liveStream?.localParticipant?.id) {
videoView.setMirror(true)
}

videoView.addTrack(stream.mediaStream, stream.trackId)
}
}
}

private fun updateMicStatus(isMicOn: Boolean, micStatusIcon: ImageView, micStatusText: TextView) {
micStatusIcon.setImageResource(if (isMicOn) R.drawable.ic_audio_on else R.drawable.ic_audio_off)
micStatusText.text = if (isMicOn) "On" else "Off"
}

private fun updateWebcamStatus(isWebcamOn: Boolean, webcamStatusIcon: ImageView, webcamStatusText: TextView, videoView: VideoView) {
webcamStatusIcon.setImageResource(if (isWebcamOn) R.drawable.ic_video_camera else R.drawable.ic_video_camera_off)
webcamStatusText.text = if (isWebcamOn) "On" else "Off"

// Show/hide video view based on webcam status
if (isWebcamOn) {
videoView.visibility = View.VISIBLE
} else {
videoView.removeTrack()
videoView.visibility = View.GONE
}
}

3. Rendering Audio Streams of Hosts​

Alongside video rendering, you can also play a participant’s audio whenever their mic is turned on.

private fun setupAudioControls() {
micButton?.setOnClickListener {
if (liveStream?.localParticipant?.isMicOn == true) {
// Turn off microphone
liveStream?.localParticipant?.disableMic()
micButton?.setImageResource(R.drawable.ic_audio_off)
} else {
// Turn on microphone
liveStream?.localParticipant?.enableMic()
micButton?.setImageResource(R.drawable.ic_audio_on)
}
}
}

liveStream?.addEventListener(object : MeetingEventListener() {
override fun onAudioDeviceChanged(audioDevice: String, availableAudioDevices: Set<String>) {
// Handle audio device changes (e.g., switching between speaker, earpiece, headphones)
activity?.runOnUiThread {
// Update UI based on current audio device
when (audioDevice) {
"SPEAKER_PHONE" -> audioOutputButton?.setImageResource(R.drawable.ic_speaker)
"EARPIECE" -> audioOutputButton?.setImageResource(R.drawable.ic_earpiece)
"WIRED_HEADSET" -> audioOutputButton?.setImageResource(R.drawable.ic_headset)
"BLUETOOTH" -> audioOutputButton?.setImageResource(R.drawable.ic_bluetooth)
}
}
}
})

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