> ## Documentation Index
> Fetch the complete documentation index at: https://docs.daily.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Network and call quality

> Monitor network quality and CPU load during a call, and test conditions before a user joins.

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`](/reference/daily-js/events/network-events#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.

```javascript theme={null}
call.on('network-quality-change', ({ networkState, networkStateReasons }) => {
  switch (networkState) {
    case 'good':
      showQualityIndicator('good');
      break;
    case 'warning':
      showQualityIndicator('warning');
      console.log('Degraded due to:', networkStateReasons);
      break;
    case 'bad':
      showQualityIndicator('bad');
      notifyUser('Your connection quality is poor.');
      break;
  }
});
```

`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).

<Note>
  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.
</Note>

### Getting detailed stats on demand

[`getNetworkStats()`](/reference/daily-js/instance-methods/get-network-stats) 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.

```javascript theme={null}
const { networkState, networkStateReasons, stats } = await call.getNetworkStats();

if (stats.latest) {
  console.log('Send packet loss:', stats.latest.totalSendPacketLoss);
  console.log('RTT (ms):', stats.latest.networkRoundTripTime);
  console.log('Worst video recv packet loss this session:', stats.worstVideoRecvPacketLoss);
}
```

Stats in `stats.latest` are updated approximately every two seconds. The object is empty early in the call before enough data has been collected.

<Warning>
  Firefox does not report send-side packet loss or audio jitter, and does not provide `availableOutgoingBitrate` or `networkRoundTripTime`.
</Warning>

### Handling connection interruptions

The [`network-connection`](/reference/daily-js/events/network-events#network-connection) event fires when a connection is established or interrupted. Every call uses two connections — `signaling` (call management) and `sfu` (audio/video) — and both reconnect automatically as long as signaling is alive.

```javascript theme={null}
call.on('network-connection', ({ type, event }) => {
  if (event === 'interrupted') {
    if (type === 'signaling') {
      // Participant will be ejected if signaling doesn't recover within ~20s
      showReconnectingBanner();
    } else {
      // SFU interruption — media will pause but reconnect automatically
      showMediaPausedIndicator();
    }
  } else if (event === 'connected') {
    hideReconnectingBanner();
  }
});
```

## 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`](/reference/daily-js/events/network-events#cpu-load-change) to detect this:

```javascript theme={null}
call.on('cpu-load-change', ({ cpuLoadState, cpuLoadStateReason }) => {
  if (cpuLoadState === 'high') {
    console.log('High CPU load — reason:', cpuLoadStateReason);

    if (cpuLoadStateReason === 'encode') {
      // Reduce outgoing video quality
      call.updateSendSettings({ video: { maxQuality: 'low' } });
    } else if (cpuLoadStateReason === 'decode') {
      // Reduce number of incoming video streams
      call.updateReceiveSettings({ '*': { video: { layer: 0 } } });
    }
  }
});
```

`cpuLoadStateReason` identifies whether encoding, decoding, or scheduling is the bottleneck, so you can target the right response. Use [`getCpuLoadStats()`](/reference/daily-js/instance-methods/get-cpu-load-stats) to poll for per-track decode stats or inspect frame encode/decode times in detail.

<Warning>
  CPU load monitoring applies to video calls only.
</Warning>

## 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()`](/reference/daily-js/instance-methods/test-call-quality) 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()`](/reference/daily-js/instance-methods/pre-auth) or [`startCamera()`](/reference/daily-js/instance-methods/start-camera) and before [`join()`](/reference/daily-js/instance-methods/join):

```javascript theme={null}
const call = Daily.createCallObject();
await call.preAuth({ url: DAILY_ROOM_URL });

const results = await call.testCallQuality();

switch (results.result) {
  case 'good':
    proceedToJoin();
    break;
  case 'warning':
    showWarning('Your connection may cause choppy audio or video.');
    offerToJoinAnyway();
    break;
  case 'bad':
    showWarning('Your connection is poor. Try a different network.');
    offerToJoinAnyway();
    break;
  case 'failed':
    showError('Could not reach Daily servers. Check your network and try again.');
    break;
  case 'aborted':
    // join() or stopTestCallQuality() was called before data collection finished
    break;
}
```

The test runs for up to 30 seconds but you can stop it early:

```javascript theme={null}
call.stopTestCallQuality();
```

If [Adaptive Bitrate](/docs/guides/architecture-and-monitoring/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'`.

### Other pre-call tests

Three additional tests are available for more targeted diagnostics:

* **[`testWebsocketConnectivity()`](/reference/daily-js/instance-methods/test-websocket-connectivity)** — 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)`](/reference/daily-js/instance-methods/test-network-connectivity)** — 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)`](/reference/daily-js/instance-methods/test-peer-to-peer-call-quality)** — 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`](/reference/daily-js/events/network-events#test-completed) event:

```javascript theme={null}
call.on('test-completed', ({ test, results }) => {
  if (test === 'call-quality') {
    console.log('Pre-call quality result:', results.result);
  }
});
```

## See also

<CardGroup>
  <Card title="Methods" icon="code" iconType="solid">
    * [testCallQuality()](/reference/daily-js/instance-methods/test-call-quality)
    * [getNetworkStats()](/reference/daily-js/instance-methods/get-network-stats)
    * [getCpuLoadStats()](/reference/daily-js/instance-methods/get-cpu-load-stats)
    * [updateSendSettings()](/reference/daily-js/instance-methods/update-send-settings)
    * [updateReceiveSettings()](/reference/daily-js/instance-methods/update-receive-settings)
  </Card>

  <Card title="Events" icon="bolt" iconType="solid">
    * [network-quality-change](/reference/daily-js/events/network-events#network-quality-change)
    * [network-connection](/reference/daily-js/events/network-events#network-connection)
    * [cpu-load-change](/reference/daily-js/events/network-events#cpu-load-change)
    * [test-completed](/reference/daily-js/events/network-events#test-completed)
  </Card>

  <Card title="Guides" icon="book-open" iconType="solid">
    * [Adaptive bitrate](/docs/guides/architecture-and-monitoring/adaptive-bitrate)
    * [Logging and metrics](/docs/guides/architecture-and-monitoring/logging-and-metrics)
  </Card>
</CardGroup>
