Scalability Guide for Large Participant Rooms
Overview
This guide explains how to optimize performance when hosting video rooms with 10+ participants by using these features:
Adaptive Subscriptions: Prioritizes audio/video streams based on who's speaking
On-Demand Media: Receives a participant's mic and webcam only when you ask for them
These features help solve common challenges in large rooms, such as excessive bandwidth consumption and poor quality for users with network constraints.
Common Challenges in Large Rooms
1. Ineffective Stream Prioritization
Problem: All streams receive equal treatment regardless of importance.
Impact:
- Users with limited bandwidth experience poor quality
- Audio quality suffers when competing with video for resources
- Important speakers may not be clearly heard or seen
Solution Features
Adaptive Subscriptions
This feature optimizes bandwidth by prioritizing active speakers and managing stream quality.
Key Benefits:
- Identifies and prioritizes dominant speakers
- Manages bandwidth constraints
- Ensures clear audio even under network limitations
- Gives highest priority to pinned participants
How It Works:
The system analyzes speaking patterns to detect active participants and adjusts stream priorities accordingly. For bandwidth-constrained users, it pauses or lowers the quality of non-essential video streams. Pinned participants always receive the highest priority in stream allocation.
Visual Illustration:

If a user experiences bandwidth constraints, Adaptive Subscriptions will pause the video streams in the following priority order:
- Muted Participants
- Least Dominant Speakers
- Dominant Speakers
If you want to achieve pin/unpin functionality, you can refer to this documentation
On-Demand Media
By default, your app receives every participant's mic and webcam as soon as they turn them on. With autoConsume = false, it receives a participant's media only when you ask for it, so you can skip the streams you do not show.
Key Benefits:
- Saves bandwidth on streams that are not on screen
- Stops receiving a participant's media without muting them for anyone else
Implementation Guide
Prerequisites
- SDK Version: 3.0.0 or later
Enabling Adaptive Subscriptions (BETA)
You can toggle this feature using these methods:
// Enable adaptive subscriptions
lifecycleScope.launch {
try {
room.enableAdaptiveSubscription()
} catch (e: VideoSDKException) {
Log.e("VideoSDK", "Enable failed: ${e.code} ${e.name}")
}
}
// Disable adaptive subscriptions
lifecycleScope.launch {
try {
room.disableAdaptiveSubscription()
} catch (e: VideoSDKException) {
Log.e("VideoSDK", "Disable failed: ${e.code} ${e.name}")
}
}
enableAdaptiveSubscription()anddisableAdaptiveSubscription()aresuspendfunctions. Call them from a coroutine, such aslifecycleScope.launch.- If the request fails, they throw
VideoSDKException. - Called before the room is joined, or while it is reconnecting, they do nothing and report
3035(ERROR_ACTION_PERFORMED_BEFORE_ROOM_JOINED) or3065(ERROR_ROOM_RECONNECTING) ononErrorinstead of throwing.
Recommended Implementation:
private val roomEventListener = object : RoomEventListener() {
override fun onRoomJoined(switchRoomId: String?) {
lifecycleScope.launch {
try {
room.enableAdaptiveSubscription()
} catch (e: VideoSDKException) {
Log.e("VideoSDK", "Enable failed: ${e.code} ${e.name}")
}
}
}
}
Stream Pause/Resume Events
When a video stream is paused or resumed, the system triggers events with specific reasons:
private val participantEventListener = object : ParticipantEventListener() {
override fun onStreamPaused(kind: StreamKind?, reason: String) {
Log.d("onStreamPaused", "reason : $reason")
}
override fun onStreamResumed(kind: StreamKind?, reason: String) {
Log.d("onStreamResumed", "reason : $reason")
}
}
Pause Reasons:
muted: Participant has turned off their audioleastDominance: Participant hasn't spoken much in the room
Resume Reasons:
adaptiveSubscriptionsDisabled: Feature was disablednetworkStable: Network conditions have improved
Developer Tip: When a stream is paused (e.g., due to network issues), always keep users in the loop by displaying a helpful UI indicator. For instance, render a message such as: "Paused: Waiting for stable connection..."
Receiving Media On Demand
Pass autoConsume = false to createRoom(). Your app then receives a participant's mic and webcam only after you call consumeMicStreams() or consumeWebcamStreams() on that participant. Until then, onStreamEnabled() does not fire for that media. The consume methods take effect only when the room was created with autoConsume set to false.
room = VideoSDK.createRoom(
roomId = roomId,
token = token,
participantName = "John Doe",
autoConsume = false,
)
Call them for the participants you show, for example from onParticipantJoined(), and again from onMediaStatusChanged() when a participant turns their mic or webcam on. Ask once per participant and kind: skip a kind that is already arriving in participant.streams or already requested.
private val roomEventListener = object : RoomEventListener() {
override fun onParticipantJoined(participant: Participant) {
receive(participant, MediaType.AUDIO)
receive(participant, MediaType.VIDEO)
participant.addEventListener(object : ParticipantEventListener() {
override fun onMediaStatusChanged(participantId: String, kind: MediaType, newStatus: Boolean) {
// they turned their mic or webcam on
if (newStatus) receive(participant, kind)
}
})
}
}
// one request per participant and kind: skip media already arriving or already asked for
private val requesting = mutableSetOf<Pair<String, MediaType>>()
private fun receive(participant: Participant, kind: MediaType) {
val key = participant.id to kind
if (participant.streams.values.any { it.kind == kind } || !requesting.add(key)) return
lifecycleScope.launch {
try {
when (kind) {
MediaType.AUDIO -> participant.consumeMicStreams()
MediaType.VIDEO -> participant.consumeWebcamStreams()
else -> Unit
}
} catch (e: VideoSDKException) {
Log.e("VideoSDK", "Could not receive $kind: ${e.code} ${e.name}")
} finally {
requesting.remove(key)
}
}
}
To stop receiving a participant's media, for example when their tile leaves the screen, call stopConsumingMicStreams() or stopConsumingWebcamStreams(). They are not muted for anyone else. All four methods are suspend functions and throw VideoSDKException when the request fails. For a complete example, see Selective Subscription.
Best Practices
- Enable adaptive subscriptions every time you join a room
- Design your UI to display indicators when a stream is paused
- Pin important participants to ensure they receive the highest streaming priority
- In large rooms, pass
autoConsume = falseand receive only the participants you show
Got a Question? Ask us on discord

