Daily provides a suite of APIs for measuring network conditions during a call and before a call starts. Use these to surface quality indicators in your UI, adapt encoding settings, or gate users from joining with poor connections.
In-call network quality
getNetworkStats()
Returns a point-in-time snapshot of network quality for the local participant:
const stats = await call . getNetworkStats ();
The resolved value is a DailyNetworkStats object:
interface DailyNetworkStats {
networkState : 'good' | 'warning' | 'bad' | 'unknown' ;
networkStateReasons : networkStateReasons [];
stats : Record < string , never > | DailyNetworkStatsData ;
/** @deprecated use networkState */
threshold : 'good' | 'low' | 'very-low' ;
/** @deprecated use networkState */
quality : number ;
}
networkState
'good' | 'warning' | 'bad' | 'unknown'
High-level assessment of current network conditions. 'unknown' is returned early in a call before enough data is available.
The specific metrics that caused a 'warning' or 'bad' state. Can include:
'sendPacketLoss' — outbound packet loss is high
'recvPacketLoss' — inbound packet loss is high
'roundTripTime' — RTT is too high
'availableOutgoingBitrate' — available bandwidth is constrained
Detailed per-metric data. Empty object early in the call. Show DailyNetworkStatsData
Most recent measurement window: Field Type Description timestampnumberUnix timestamp (ms) of the measurement recvBitsPerSecondnumber | nullTotal inbound bitrate sendBitsPerSecondnumber | nullTotal outbound bitrate availableOutgoingBitratenumber | nullEstimated available send bandwidth networkRoundTripTimenumber | nullRTT in milliseconds videoRecvBitsPerSecondnumber | nullVideo inbound bitrate videoSendBitsPerSecondnumber | nullVideo outbound bitrate audioRecvBitsPerSecondnumber | nullAudio inbound bitrate audioSendBitsPerSecondnumber | nullAudio outbound bitrate videoRecvPacketLossnumber | nullInbound video packet loss (0–1) videoSendPacketLossnumber | nullOutbound video packet loss (0–1) audioRecvPacketLossnumber | nullInbound audio packet loss (0–1) audioSendPacketLossnumber | nullOutbound audio packet loss (0–1) totalSendPacketLossnumber | nullCombined send packet loss totalRecvPacketLossnumber | nullCombined recv packet loss videoRecvJitternumber | nullInbound video jitter (ms) videoSendJitternumber | nullOutbound video jitter (ms) audioRecvJitternumber | nullInbound audio jitter (ms) audioSendJitternumber | nullOutbound audio jitter (ms)
Peak inbound video packet loss seen this session.
Peak outbound video packet loss seen this session.
Peak inbound audio packet loss seen this session.
Peak outbound audio packet loss seen this session.
Peak inbound video jitter seen this session.
Peak outbound video jitter seen this session.
Peak inbound audio jitter seen this session.
Peak outbound audio jitter seen this session.
averageNetworkRoundTripTime
Session-average RTT in milliseconds.
network-quality-change event
Subscribe to real-time quality updates instead of polling getNetworkStats():
call . on ( 'network-quality-change' , ( event ) => {
// event is DailyNetworkStats merged with DailyEventObjectBase
const { networkState , networkStateReasons , stats } = event ;
switch ( networkState ) {
case 'good' :
showQualityIndicator ( 'green' );
break ;
case 'warning' :
showQualityIndicator ( 'yellow' );
console . log ( 'Quality degraded due to:' , networkStateReasons );
break ;
case 'bad' :
showQualityIndicator ( 'red' );
showUserWarning ( 'Your connection quality is poor.' );
break ;
}
});
Displaying a network quality indicator
function renderNetworkBadge ( networkState : string ) {
const badges = {
good: { label: 'Good' , color: '#22C55E' },
warning: { label: 'Fair' , color: '#F59E0B' },
bad: { label: 'Poor' , color: '#EF4444' },
unknown: { label: '…' , color: '#6B7280' },
};
const badge = badges [ networkState ] ?? badges . unknown ;
document . getElementById ( 'network-badge' ) ! . textContent = badge . label ;
document . getElementById ( 'network-badge' ) ! . style . color = badge . color ;
}
call . on ( 'network-quality-change' , ({ networkState }) => {
renderNetworkBadge ( networkState );
});
CPU load monitoring
getCpuLoadStats()
const cpuStats = await call . getCpuLoadStats ();
Returns a DailyCpuLoadStats object:
interface DailyCpuLoadStats {
cpuLoadState : 'low' | 'high' ;
cpuLoadStateReason : 'encode' | 'decode' | 'scheduleDuration' | 'none' ;
stats : { latest : { ... } };
}
Whether the device is under significant CPU pressure from the call.
cpuLoadStateReason
'encode' | 'decode' | 'scheduleDuration' | 'none'
The primary cause of high CPU load:
'encode' — video encoding is consuming too much CPU
'decode' — video decoding is consuming too much CPU
'scheduleDuration' — thread scheduling delays are high
'none' — no specific reason (state is 'low')
cpu-load-change event
call . on ( 'cpu-load-change' , ({ cpuLoadState , cpuLoadStateReason }) => {
if ( cpuLoadState === 'high' ) {
console . warn ( `High CPU load: ${ cpuLoadStateReason } ` );
// Consider reducing video quality or tile count
}
});
Network topology
Daily supports two network topologies: SFU (server-forwarded, the default for most rooms) and peer-to-peer.
// Get current topology
const { topology } = await call . getNetworkTopology ();
// topology: 'sfu' | 'peer' | 'none'
// Request a topology switch
const { workerId , error } = await call . setNetworkTopology ({
topology: 'sfu' ,
});
Switching topology mid-call causes a brief reconnection for all participants. Use with caution in active calls.
Pre-call tests
Run these tests before the user joins to validate connectivity and set expectations.
testCallQuality()
Simulates an SFU call to measure round-trip time, packet loss, and available bitrate:
const results = await call . testCallQuality ();
// results: DailyCallQualityTestResults
type DailyCallQualityTestResults =
| { result : 'good' | 'warning' | 'bad' ; data : DailyCallQualityTestData ; secondsElapsed : number }
| { result : 'aborted' ; secondsElapsed : number }
| { result : 'failed' ; errorMsg : string ; secondsElapsed : number };
interface DailyCallQualityTestData {
maxRoundTripTime : number | null ;
avgRoundTripTime : number | null ;
avgSendPacketLoss : number | null ;
avgAvailableOutgoingBitrate : number | null ;
avgSendBitsPerSecond : number | null ;
}
To stop the test early:
call . stopTestCallQuality ();
testPeerToPeerCallQuality()
Tests peer-to-peer connection quality using a live video track:
const stream = await navigator . mediaDevices . getUserMedia ({ video: true });
const [ videoTrack ] = stream . getVideoTracks ();
const results = await call . testPeerToPeerCallQuality ({
videoTrack ,
duration: 15 , // seconds (optional)
});
testWebsocketConnectivity()
Checks WebSocket connectivity to Daily’s regional infrastructure:
const results = await call . testWebsocketConnectivity ();
// {
// result: 'passed' | 'failed' | 'warning' | 'aborted',
// passedRegions: string[],
// failedRegions: string[],
// abortedRegions: string[],
// }
Stop the test early:
call . abortTestWebsocketConnectivity ();
testNetworkConnectivity()
Tests general network connectivity using a video track to measure throughput:
const stream = await navigator . mediaDevices . getUserMedia ({ video: true });
const [ videoTrack ] = stream . getVideoTracks ();
const results = await call . testNetworkConnectivity ( videoTrack );
// { result: 'passed' | 'failed' | 'aborted' }
Stop the test early:
call . abortTestNetworkConnectivity ();
test-completed event
All pre-call tests emit test-completed when they finish:
call . on ( 'test-completed' , ({ test , results }) => {
// test: 'call-quality' | 'p2p-call-quality' | 'network-connectivity' | 'websocket-connectivity'
console . log ( `Test " ${ test } " finished:` , results );
});
Example: pre-call quality check
async function runPreCallCheck () {
const call = Daily . createCallObject ();
// Test WebSocket connectivity first
const wsResults = await call . testWebsocketConnectivity ();
if ( wsResults . result === 'failed' ) {
return { canJoin: false , reason: 'Cannot reach Daily servers' };
}
// Test call quality
const qualityResults = await call . testCallQuality ();
if ( qualityResults . result === 'failed' ) {
return { canJoin: false , reason: 'Pre-call test failed' };
}
return {
canJoin: true ,
quality: qualityResults . result , // 'good' | 'warning' | 'bad'
data: qualityResults . result !== 'aborted' ? qualityResults . data : null ,
};
}