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: Intelligently prioritizes audio/video streams based on who's speaking
On-Demand Streams: 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
- Intelligently 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 strategically 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 Streams
By default, your app receives every participant's mic and webcam as soon as they turn them on. With on-demand streams, 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: 5.0.0 or later
Enabling Adaptive Subscriptions (BETA)
You can toggle this feature using these methods:
// Enable adaptive subscriptions
try {
await room.enableAdaptiveSubscription();
} on VideoSDKError catch (error) {
debugPrint("enableAdaptiveSubscription failed: ${error.code} ${error.name}");
}
// Disable adaptive subscriptions
try {
await room.disableAdaptiveSubscription();
} on VideoSDKError catch (error) {
debugPrint("disableAdaptiveSubscription failed: ${error.code} ${error.name}");
}
- Both methods return a
Futurethat throws aVideoSDKErrorwhen the SDK refuses the call, soawaitthem insidetry/catchafter the room is joined. - On Android, a call made while the room reconnects reports
3065onEvents.errorinstead of throwing.
Recommended Implementation:
@override
void initState() {
super.initState();
// Create instance of Room (Room)
initRoom();
WidgetsBinding.instance.addObserver(this);
}
initRoom() async {
// createRoom called
Room room = VideoSDK.createRoom(
roomId: widget.roomId,
token: widget.token,
participantName: widget.displayName,
micEnabled: widget.micEnabled,
camEnabled: widget.camEnabled,
multiStream: true,
);
// register events
registerRoomEvents(room);
}
void registerRoomEvents(Room _room) {
// Called when joined in room
_room.on(
Events.roomJoined,
() async {
try {
await _room.enableAdaptiveSubscription();
} on VideoSDKError catch (error) {
debugPrint("enableAdaptiveSubscription failed: ${error.code} ${error.name}");
}
},
);
}
Stream Pause/Resume Events
When a stream you receive is paused or resumed, the room triggers events with specific reasons. These events do not say which participant's stream it was, so register them on the Room, not in a participant tile.
room.on(Events.streamPaused, (String? kind, String? reason) {
// when stream will get paused this event will be received
});
room.on(Events.streamResumed, (String? kind, String? reason) {
// when stream will get resumed this event will be received
});
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
The reason can also be empty, so handle the reasons you need and ignore the others.
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, Events.streamEnabled does not fire for that media.
Room room = VideoSDK.createRoom(
roomId: widget.roomId,
token: widget.token,
participantName: widget.displayName,
autoConsume: false,
);
Both methods return a Future that throws a VideoSDKError, so await them inside try/catch. Call them after the room is joined: from your Events.roomJoined handler for participants who were already in the room, and from Events.participantJoined for those who join later. In this example, _joined is a bool field of your State that starts as false.
void registerConsumeEvents(Room room) {
room.on(Events.roomJoined, () async {
_joined = true;
// Participants who were already in the room. Loop over a copy,
// because the map changes when someone joins or leaves during an await.
for (final participant in room.participants.values.toList()) {
await receiveMedia(participant);
}
});
room.on(Events.participantJoined, (Participant participant) async {
// Participants who join later
if (_joined) await receiveMedia(participant);
});
}
Future<void> receiveMedia(Participant participant) async {
try {
await participant.consumeMicStreams();
await participant.consumeWebcamStreams();
} on VideoSDKError catch (error) {
debugPrint("Could not receive media of ${participant.displayName}: ${error.code} ${error.name}");
}
}
To stop receiving a participant's media, for example when their tile leaves the screen, call stopConsumingMicStreams() or stopConsumingWebcamStreams(). These calls take effect only when the room was created with autoConsume set to false. The participant is not muted for anyone else. Call the consume method again to receive their media again.
Saving Upload Bandwidth
Pass dynaCast: true to createRoom(). The server then lowers the quality of the webcam video you send while no one is watching its larger layers, and raises it again when someone is. It has no effect when multiStream is false.
Best Practices
- Enable adaptive subscriptions at the start of every 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

