Skip to main content
The SDK provides three ways to get network quality, from coarse to fine: The SDK also fires events proactively when connection quality changes or when CPU/bandwidth is constrained, so your app can subscribe passively instead of polling. Connection quality events reuse the existing ChannelEvent system; handle them directly in onNotifyChannelEvent:

2. Query on demand

2.1 Connection quality evaluation getConnectionQuality

The SDK evaluates over a 2-second sliding window, scoring across multiple metrics: packet loss rate, RTT, jitter, video freezes, uplink bandwidth pressure, qualityLimitationReason, and more.

2.2 Overall network statistics getNetworkStats

:::note The delay and up_level fields from older versions have been removed: delay is now split into the more precise rtt_up and rtt_down, and up_level is replaced by QualityEvaluation.uplink returned by getConnectionQuality(). :::

2.3 Full metric snapshot getStreamMetric

3. Track-level raw statistics

The stats field of each track in StreamMetric corresponds to WebRTC’s native sender/receiver stats:
Most apps only need to show a “network status indicator”. The recommended combination:

5. Handling poor networks

To handle a poor network, first distinguish two things: direction and cause.
  • A poor uplink means you can’t send, and you can degrade proactively (lower bitrate, switch to the low stream, turn off the camera); a poor downlink means you can’t receive, and all you can do is receive less (switch to the low stream, unsubscribe from video). So look at both the uplink and downlink directions, not just overall.
  • BANDWIDTH_CONSTRAINED means insufficient bandwidth, and CPU_CONSTRAINED means the device’s encoder can’t keep up; they call for different handling.

5.1 Show network status

Mapping overall to a status indicator is enough. Don’t show raw data such as packet loss rate or RTT to end users: Uplink and downlink problems need different hint messages:
Debounce the hints: toast only after poor persists for a few seconds, not on every fluctuation. The SDK already debounces the BANDWIDTH_CONSTRAINED / CPU_CONSTRAINED events; we recommend rate-limiting your popups once more in your app. Degrade step by step, from light to heavy:
  1. Encoder adaptation: done automatically by the browser, on by default, no action needed. If you don’t want the resolution to be lowered automatically, change the degradation mode with degradationPreference (see 5.4).
  2. Switch to the low stream / lower resolution: if you publish simulcast, you can switch to the low stream (320×180, about 250 Kbps). See Simulcast and resolution.
  3. Turn off the camera, keep audio only: audio bitrate is low (about 32 Kbps) and can almost always be kept on a poor network.
  1. Stop publishing entirely: unpublishLocalTrack(localVideoTrack) is more thorough than turning off the camera and releases uplink encoding and sending resources.
On CPU_CONSTRAINED, prefer lowering resolution / frame rate (to reduce encoding load); on BANDWIDTH_CONSTRAINED, lowering either bitrate or resolution works. Turning off the camera changes the shape of the call, so we recommend notifying the user before doing it rather than doing it silently—especially in scenarios such as emergency command and law enforcement recording.
  1. First check whether the problem is on the remote side: only if you have downlink packet loss is your receiving path poor. If you have almost no packet loss and just one person’s video is freezing, it’s usually that person’s uplink; unsubscribing won’t help, so just show “the other party’s network is poor”. The SDK’s scoring already distinguishes these two cases and won’t lower your downlink level because of someone else’s problem.
  2. When the publisher sends simulcast, the SFU switches layers automatically: based on each subscriber’s own downlink bandwidth and packet loss, the SFU automatically switches it to the appropriate high / low stream, with no manual switching on the subscriber side; if only a single stream is published, it can’t degrade automatically. See Simulcast and resolution.
  3. Unsubscribe from video, receive audio only:
  1. Multi-party calls: subscribe only to the current speaker’s video, and receive audio only from everyone else.

5.4 Control the degradation mode: degradationPreference

VideoPublishOptions.degradationPreference of publishLocalTrack decides what to sacrifice on a poor network: If you don’t want the resolution to be lowered automatically, set it to maintain-resolution:
Among the built-in presets, camera 720p / 1080p and all screen sharing presets default to maintain-resolution; 360p / 180p don’t set it and use the browser default.

5.5 Connection lost (lost)

overall === 'lost' means media is interrupted. Start reconnecting and show the user “Reconnecting”; after recovery, refresh the status indicator and restore any camera or subscriptions you turned off earlier.

5.6 Quick reference

5.7 Complete example

Putting it all together: configure the degradation strategy when publishing, and at runtime subscribe to events to update the status indicator, show direction-specific hints, and degrade and restore as needed.
publishLocalTrack / disableLocalTrack / enableLocalTrack in the example are all existing SDK methods. Operations that change the shape of the call, such as turning off the camera, are handed to the user for confirmation via a button rather than done silently; for regular calls and similar scenarios you can make them automatic. Implement the downlink “audio only” fallback yourself following the idea in the comments (use unsubscribeRemoteTrack to unsubscribe from video and keep audio).