Skip to main content
Version: 5.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: 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:

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 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:

main.dart
// 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}");
}
note
  • Both methods return a Future that throws a VideoSDKError when the SDK refuses the call, so await them inside try/catch after the room is joined.
  • On Android, a call made while the room reconnects reports 3065 on Events.error instead of throwing.

Recommended Implementation:

main.dart
@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_screen.dart
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 audio
  • leastDominance: Participant hasn't spoken much in the room

Resume Reasons:

  • adaptiveSubscriptionsDisabled: Feature was disabled
  • networkStable: 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.

main.dart
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.

main.dart
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: false and receive only the participants you show

Got a Question? Ask us on discord