Skip to main content
Version: 3.x.x

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:

Viewport illustration

If a user experiences bandwidth constraints, Adaptive Subscriptions will pause the video streams in the following priority order:

  1. Muted Participants
  2. Least Dominant Speakers
  3. 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}")
}
}
note
  • enableAdaptiveSubscription() and disableAdaptiveSubscription() are suspend functions. Call them from a coroutine, such as lifecycleScope.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) or 3065 (ERROR_ROOM_RECONNECTING) on onError instead 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 audio
  • leastDominance: Participant hasn't spoken much in the room

Resume Reasons:

  • adaptiveSubscriptionsDisabled: Feature was disabled
  • networkStable: 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 = false and receive only the participants you show

Got a Question? Ask us on discord