Skip to main content
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.
networkStateReasons
networkStateReasons[]
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
stats
DailyNetworkStatsData
Detailed per-metric data. Empty object early in the call.

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: { ... } };
}
cpuLoadState
'low' | 'high'
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,
  };
}