Skip to main content
Version: 2.x.x

Simulcast (Adaptive Bitrate) - React Native

Simulcast is a technique used in real-time video communication where a single camera publishes several versions of the same video at once. Each version is called a layer, and has its own resolution and bitrate.

VideoSDK then delivers the appropriate layer to each participant, based on their requirements and network conditions.

Why Simulcast Matters​

Without simulcast, one camera publishes a single version of its video, and everyone receives it. That version has to suit every participant at once, so it is set either high enough for the participant with the best connection, which leaves the participant on a weak connection with frozen video, or low enough for the weakest connection, which leaves everyone else with a soft picture they did not need to settle for.

With simulcast, VideoSDK sends each participant the layer that fits them. It moves a participant to a lower layer when their connection drops, and back up when it recovers.

Configuring Simulcast​

Simulcast is configured when creating a video track using createCameraVideoTrack(). The two parameters that control simulcast are multiStream and maxLayer.

If you don't pass a custom camera track, the camera that VideoSDK opens for you takes multiStream from the config of RoomProvider instead. There it defaults to true as well. Set multiStream: false to publish a single layer. That camera opens at 1280x720, or at 960x540 when maxResolution in the same config is sd.

multiStream​

multiStream determines whether a camera track publishes multiple video layers or a single layer. For a track created with createCameraVideoTrack(), it is true by default, so simulcast is enabled unless you explicitly disable it.

One participant publishes several layers at once, and VideoSDK sends each viewer the layer that suits them

Setting multiStream: false publishes a single video layer. Every participant then receives the same layer regardless of their screen size or network conditions.

One participant publishes a single 720p stream, and every viewer receives 720p

The number of layers depends on the resolution of the track's encoderConfig.

encoderConfig ResolutionLayers
Less than 480 pixels on the longer edge1
480–959 pixels2
960 pixels or higher3

For example, a 1280x720 track can publish:

320x180 → Low
640x360 → Medium
1280x720 → High

On iOS, VideoSDK scales the camera video to the encoderConfig resolution before sending it, so a track publishes the layers listed above. On Android, the layers are scaled down from the video the camera actually captures. If the device cannot capture the requested resolution, the layers are smaller than listed.

maxLayer​

maxLayer limits the maximum number of layers published by a track. It accepts 2 or 3, defaults to 3, and applies only when multiStream is true.

For example, a 1280x720 track publishes:

maxLayer: 3 → 320x180, 640x360, 1280x720
maxLayer: 2 → 320x180, 1280x720

With maxLayer: 2, the middle layer is omitted, so the track publishes only the lowest and highest layers, which preserves both a high-quality option and a low-bandwidth fallback while encoding one stream fewer. This is worth reaching for on devices where encoding three streams costs more CPU or battery than the middle layer is worth.

info

maxLayer never adds layers beyond what the encoderConfig resolution allows, and there is no middle layer to drop on a track that publishes two. On h360p_w640p or h480p_w640p, maxLayer: 2 and maxLayer: 3 give the same result.

Refer to the Optimize Video Tracks guide for the full set of createCameraVideoTrack() parameters and for examples of passing a custom track to a room.

Selecting the Video Quality Layer​

Once multiple video layers are published, your application can control which layer each participant receives.

danger

setQuality() chooses between published layers, so it has nothing to act on when a camera publishes a single layer. That is the case for a track created with multiStream: false, and for the camera VideoSDK opens for you while multiStream in the RoomProvider config is false. Every participant receives that one layer.

setQuality() sets the layer for a remote participant by name, accepting low, med, or high. Reach for it when your layout already tells you how much detail a view needs, so you can set the level from your own rules. A speaker in the main stage takes high, and the same participant in a sidebar takes low.

Refer to the Layout and Grid Management guide for recommended quality levels for different layouts.

Example​

import { useParticipant } from "@videosdk.live/react-native-sdk";

function ParticipantView({ participantId }) {
const { setQuality } = useParticipant(participantId);

const lowerQuality = async () => {
try {
await setQuality("low");
} catch (err) {
console.error("setQuality failed:", err);
}
};

return <>...</>;
}
note

The requested quality is a preference, not a guarantee. If the participant's network cannot support the requested layer, VideoSDK may deliver a lower layer and move back to the requested quality when the connection improves.

Capping the Quality You Send​

You can also limit what your own camera sends, which saves upload bandwidth.

  • setWebcamQuality() caps the quality of the camera video you send. high sends every layer, med stops the largest one, and low sends only the smallest, so other participants receive at most that quality. The values are those of Constants.VideoQuality. Call it from useRoom(). It returns a Promise that rejects if the cap can't be applied, for example with ERROR_WEBCAM_NOT_ENABLED (3062) while your camera is off. The cap stays in force when you turn the camera off and on again.
  • dynaCast, a config option of RoomProvider, lets VideoSDK lower the quality of the camera video you send while no participant is watching its larger layers, and raise it again when someone is. It defaults to false, and never goes above the cap set with setWebcamQuality().

Neither has any effect on a camera that publishes a single layer, such as one with multiStream set to false or a codec other than VP8.

Example​

import { RoomProvider, useRoom } from "@videosdk.live/react-native-sdk";

export default function App() {
return (
<RoomProvider
config={{
roomId: "room-id",
participantName: "Test User",
micEnabled: true,
camEnabled: true,
multiStream: true,
dynaCast: true,
}}
token="your-token"
>
<RoomView />
</RoomProvider>
);
}

function RoomView() {
const { setWebcamQuality } = useRoom();

// Call this after you join, while your camera is on
const capCameraQuality = async () => {
try {
await setWebcamQuality("med");
} catch (err) {
// For example, 3062 ERROR_WEBCAM_NOT_ENABLED while your camera is off
console.error("setWebcamQuality failed:", err);
}
};

return <>...</>;
}

Checking the Current Video Layer​

Use getVideoStats() to check which video quality layer you are receiving from a remote participant.

The returned stats include:

  • currentSpatialLayer — the layer currently being received.
  • preferredSpatialLayer — the layer that was requested.
  • currentTemporalLayer — the temporal layer currently being received.
  • preferredTemporalLayer — the temporal layer that was requested.

Layers are numbered from 0, and a higher number is a higher-quality layer. On a track publishing three layers, 2 is the sharpest and 0 is the lightest. When currentSpatialLayer is below preferredSpatialLayer, a lighter layer than the one requested is being delivered, usually because the connection does not support the requested one yet.

A field is null until its layer is known. For example, preferredSpatialLayer stays null until a layer is requested for that participant, such as with setQuality().

The stats also include metrics such as bitrate, codec, size, rtt, jitter, and packetsLost, plus limitation for your own video, which can help diagnose the current video quality.

Refer to the Understanding Call Quality guide for more information about these metrics.

Example​

import { useParticipant } from "@videosdk.live/react-native-sdk";

function ParticipantLayer({ participantId }) {
const { getVideoStats } = useParticipant(participantId);

const checkLayer = async () => {
try {
const stats = await getVideoStats();
console.log("Layer in use:", stats?.[0]?.currentSpatialLayer);
console.log("Layer requested:", stats?.[0]?.preferredSpatialLayer);
} catch (err) {
console.error("getVideoStats failed:", err);
}
};

return <>...</>;
}
note

These statistics are a snapshot taken when the method is called. To follow layer changes, call getVideoStats() periodically, for example once a second.

Simulcast and Codecs​

The codec a track is encoded with determines whether it can publish layers at all.

  • VP8, the default, supports multiStream, so a track using it publishes layers as described above.
  • Refer to the Video Codecs guide for which codecs support multiStream on React Native, and for how to handle the cases where a codec is not supported.

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