Skip to main content
Daily provides events and methods for tracking call quality in real time and for running pre-call tests before a user joins. This guide covers the most common patterns for each.

Monitoring quality during a call

Reacting to quality changes

The network-quality-change event fires whenever Daily’s assessment of the local participant’s network changes. This is the recommended way to drive a quality indicator in your UI — no polling required.
networkState is calculated from packet loss, round-trip time, and available outgoing bitrate, averaged over a ~30-second rolling window. When the state is 'warning' or 'bad', networkStateReasons tells you which metrics are responsible — 'sendPacketLoss', 'recvPacketLoss', 'roundTripTime', or 'availableOutgoingBitrate' — which can help you decide how to respond (for example, prompting a user to check their connection vs. proactively reducing video quality).
In Daily Prebuilt, 'warning' triggers automatic bandwidth reduction and 'bad' disables the local camera. In a custom UI you’re responsible for any adaptive behavior.

Getting detailed stats on demand

getNetworkStats() returns a point-in-time snapshot with per-stream bitrates, packet loss, jitter, and RTT — useful if you want to log detailed diagnostics, build an advanced quality panel, or inspect conditions at a specific moment.
Stats in stats.latest are updated approximately every two seconds. The object is empty early in the call before enough data has been collected.
Firefox does not report send-side packet loss or audio jitter, and does not provide availableOutgoingBitrate or networkRoundTripTime.

Handling connection interruptions

The network-connection event fires when a connection is established or interrupted. Every call uses two connections: signaling, a websocket connection used for call management and messaging, and sfu, a WebRTC connection used for audio and video. Both reconnect automatically. sfu keeps retrying indefinitely; signaling retries twice, and if that fails, ends the call with an error event (type === 'connection-error') followed by left-meeting. Since reconnection is already handled for you, there’s no need to build your own retry logic around signaling interruption events. Depending on your use case, a retry button driven off that error event is a better way to give users a manual fallback if the automatic retries don’t succeed.
The server only removes a stale participant after about 35-40 seconds with no signal from them, so they may appear as connected to other participants for a while after they’ve actually left the call. This is particularly common with Firefox, where closing the laptop lid doesn’t cleanly signal that the participant has left the meeting. If they rejoin before the server has cleaned up the stale session, they’ll briefly appear in the meeting twice.

Monitoring CPU load

High CPU load can degrade video quality independently of network conditions, particularly on lower-powered devices or in large calls with many incoming video streams. Listen for cpu-load-change to detect this:
cpuLoadStateReason identifies whether encoding, decoding, or scheduling is the bottleneck, so you can target the right response. Use getCpuLoadStats() to poll for per-track decode stats or inspect frame encode/decode times in detail.
CPU load monitoring applies to video calls only.

Testing quality before joining

Running a pre-call test lets you warn users about poor conditions before they enter a call, or initialize bandwidth settings appropriately.

testCallQuality()

testCallQuality() is the recommended pre-call test. It connects to a private Daily room, streams video to Daily’s infrastructure for up to 30 seconds, and returns a verdict based on packet loss, round-trip time, and available bitrate. Call it after preAuth() or startCamera() and before join():
The test runs for up to 30 seconds but you can stop it early:
If Adaptive Bitrate is enabled (the default for 1:1 calls), the test results are used automatically at join time to initialize the highest simulcast layer appropriately — so running the test improves the call start even if the result is 'good'.

Setting bandwidth expectations at join time

If you already know what a client’s connection can handle — from your own measurements, or because you control the deployment environment — you can skip the pre-call test and tell Daily directly. On the receive side, set initialReceiveBitrate in the receiveSettings call property to your expected download bandwidth in bits per second. The SFU starts its estimate of how much it can send you from this value, so incoming video ramps up faster after joining or reconnecting. On the send side, startBitrate in the send settings controls the bitrate the video encoder starts at before bandwidth estimation adapts it.
initialReceiveBitrate only applies when joining — it can’t be changed with updateReceiveSettings() — and a testCallQuality() measurement taken before joining takes precedence over it. Both fields are available in daily-js 0.94.0 and later.

Other pre-call tests

Three additional tests are available for more targeted diagnostics:
  • testWebsocketConnectivity() — Checks whether WebSocket connections can be established with Daily’s signaling servers across AWS regions, without requiring a camera or a room. This is the right first check if you’re building for environments where corporate firewalls or VPNs might block WebSockets — a 'failed' result here means the call can’t even get started.
  • testNetworkConnectivity(videoTrack) — Checks whether a TURN connection can be established with Daily’s infrastructure. A step beyond WebSocket connectivity, useful for stricter network environments. Returns a simple 'passed' / 'failed' rather than a quality grade.
  • testPeerToPeerCallQuality(options) — Returns a quality verdict ('good' / 'warning' / 'bad') using TURN servers rather than an actual Daily room session. Runs faster (15 seconds by default) and doesn’t require a room URL, but it measures receive-side metrics rather than send-side, and unlike testCallQuality(), it doesn’t seed Adaptive Bitrate for the subsequent call.

Listening for results via event

If you’re building on top of Daily Prebuilt, it runs testCallQuality() automatically in its prejoin UI. You can read the results without calling the method yourself by listening for the test-completed event:

See also