Monitoring quality during a call
Reacting to quality changes
Thenetwork-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.latest are updated approximately every two seconds. The object is empty early in the call before enough data has been collected.
Handling connection interruptions
Thenetwork-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 forcpu-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.
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():
'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, setinitialReceiveBitrate 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 unliketestCallQuality(), 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 runstestCallQuality() automatically in its prejoin UI. You can read the results without calling the method yourself by listening for the test-completed event: