Skip to main content
Version: 1.x.x

Simulcast (Adaptive Bitrate) - Javascript

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.

Simulcast removes that compromise. Because several layers are already being published, VideoSDK can give each participant a different one, and can move a participant to a lighter layer the moment their connection tightens and back up when it recovers. Nobody's experience is limited by anybody else's network.

Configuring Simulcast​

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

multiStream​

multiStream determines whether a camera track publishes multiple video layers or a single layer. It is true by default, so simulcast is enabled unless you explicitly disable it.

One participant publishes 720p, 360p, and 180p layers, 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 that the camera actually captures.

Capture 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

The actual layers depend on the resolution supported by the device.

For example, if an application requests h1080p_w1920p but the device can only capture 320x180, the track publishes only the resolution supported by the device.

maxLayer​

maxLayer limits the maximum number of layers published by a track. It accepts 2 or 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 capture 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 produce the same result.

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

Selecting the Video Quality Layer​

Once multiple video layers are published, your application can control which layer each participant receives. VideoSDK provides two ways to select the appropriate layer:

  • Manual selection using setQuality()
  • Automatic selection using renderVideo() with maxQuality: "auto"
danger

Both controls choose between published layers, so they have nothing to act on when a track was created with multiStream: false. That track publishes one layer, and every participant receives it.

Manual Quality Selection​

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​

meeting.on("participant-joined", async (participant) => {
try {
await participant.setQuality("low");
} catch (err) {
console.error("setQuality failed:", err);
}
});
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.

Automatic Quality Selection​

renderVideo() returns a <div> that renders the participant's stream and manages the layer for you. Pass maxQuality: "auto", which is the default, to let VideoSDK select the layer based on the size of the video container.

This is useful when video views dynamically change size or when managing quality manually for each participant would add unnecessary complexity.

Example​

const videoContainer = document.getElementById("videoContainer");

meeting.on("participant-joined", (participant) => {
participant.on("stream-enabled", (stream) => {
if (stream.kind == "video") {
const videoElement = participant.renderVideo({
type: "video",
maxQuality: "auto", // "high" | "med" | "low"
});

videoContainer.appendChild(videoElement);
}
});
});
note

Automatic quality selection is handled by renderVideo(). If you create a <video> element yourself and attach the stream directly, use setQuality() to select the layer.

caution

maxQuality: "auto" and setQuality() both decide the layer for the same view, so use one or the other. With maxQuality: "auto" in place, set a fixed level through maxQuality rather than calling setQuality().

Refer to the Scalability for Large Participant guide for more information about renderVideo().

Monitoring Video Quality Changes​

Use the video-quality-changed event to detect when the quality layer received by a participant changes.

The event is triggered when the quality changes either because your application requested a different quality or because VideoSDK adjusted the layer based on network conditions.

It provides:

  • currentQuality — the quality layer currently being received.
  • prevQuality — the previously received quality layer.

Both values are HIGH, MEDIUM, or LOW.

The event fires only for tracks that publish more than one layer. A track created with multiStream: false never triggers it, because there is no other layer to move to.

Example​

meeting.on("participant-joined", (participant) => {
participant.on("video-quality-changed", (data) => {
const { currentQuality, prevQuality } = data;
console.log(`Video quality moved from ${prevQuality} to ${currentQuality}`);
});
});

Checking the Current Video Layer​

Use getVideoStats() to check which video quality layer a participant is currently receiving.

The returned stats include:

  • currentSpatialLayer — the layer currently being received.
  • preferredSpatialLayer — the layer that was requested, either by setQuality() or by the automatic selection of renderVideo().
  • 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.

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

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

Example​

const participant = Array.from(meeting.participants.values())[0];

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

These statistics are a snapshot taken when the method is called. To follow a change as it happens, listen for the video-quality-changed event instead of polling.

Simulcast and Codecs​

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

  • VP8, the default, and H264 both support multiStream, so a track using either publishes layers as described above.
  • Currently, VideoSDK doesn't support multiStream for VP9 and AV1. multiStream defaults to true, so set multiStream: false when you create a VP9 or AV1 track. Otherwise VideoSDK sets it to false for you and emits the ERROR_MULTISTREAM_NOT_SUPPORTED error event. Those codecs compress more efficiently, which is their own route to good quality on a modest connection, and it applies to the single layer they publish.

Refer to the Video Codecs guide to compare the four codecs and 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