> ## 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.

# Debugging call issues

> Diagnose connection failures, device problems, and audio or video quality issues in Daily calls, starting from the symptom.

Start from the symptom. Each section below covers one common complaint and the signals to check for it, using the same data Daily's support team uses.

Grab the session ID for the affected call first. Every check below is easier with it. See [finding your session ID](/docs/guides/troubleshooting/before-contacting-support#find-your-session-id).

## Start with the session in the dashboard

The [Daily dashboard](https://dashboard.daily.co) shows call quality data for every session on your domain. The [dashboard overview](/docs/guides/architecture-and-monitoring/experiment-in-the-dashboard#sessions) has screenshots.

<Steps>
  <Step title="Open the session">
    On the **Sessions** page, find the session by session ID, room name, or time.
  </Step>

  <Step title="Find the affected participant">
    The session view lists every participant with their packet loss and join time. Click the one who reported the problem.
  </Step>

  <Step title="Read their charts and event log">
    The participant view shows their browser, OS, and network type, packet loss and bitrate charts for the whole session, and a full event log from their point of view.
  </Step>
</Steps>

Scan the packet loss and bitrate charts first, then the event log for `videoQualityLimReason`, `cpu load state change`, `network-connection` interruptions, and `Connection downlink`. The [logging and metrics guide](/docs/guides/architecture-and-monitoring/logging-and-metrics) explains each [log line](/docs/guides/architecture-and-monitoring/logging-and-metrics#logs) and [metric](/docs/guides/architecture-and-monitoring/logging-and-metrics#metrics), including how to tell a network cause from a CPU one. Keep it open alongside this page.

## "I couldn't join the call"

Work through these in order:

1. **Check the room URL and token.** The [`error`](/reference/daily-js/events/error-events#error) event says why a join was rejected. Common types: `no-room` (bad URL or deleted room), `exp-room` / `exp-token` (expired), `nbf-room` / `nbf-token` (not valid yet), `not-allowed` (private room without a valid token), and `meeting-full`.
2. **Look for `connection-error`.** That `error` type means the network path failed, not the room. `details.on` names the step: `load`, `room-lookup`, `join`, `reconnect`, `move`, or `rtc-connection`.
3. **Suspect the network.** Corporate firewalls and VPNs can block the WebSocket or UDP connections a call needs. Run [`testWebsocketConnectivity()`](/reference/daily-js/instance-methods/test-websocket-connectivity) first: a `failed` result means the call can't get started on that network. Then run [`testNetworkConnectivity()`](/reference/daily-js/instance-methods/test-network-connectivity) to check whether a TURN connection works. The [firewall and allowed IP guide](/docs/guides/privacy-and-security/corporate-firewalls-nats-allowed-ip-list) lists what to unblock.
4. **Run a full pre-call test.** [`testCallQuality()`](/reference/daily-js/instance-methods/test-call-quality) streams real media to Daily for up to 30 seconds and returns a verdict. The [network and call quality guide](/docs/daily-js/guides/network-quality) covers all the pre-call tests.

<Note>
  A user who never reaches Daily's servers has no session and nothing in the dashboard. Their browser console is your only evidence. Capture it.
</Note>

## "I couldn't select my camera or microphone"

Device failures fire the [`camera-error`](/reference/daily-js/events/settings-events#camera-error) event. daily-js normalizes the browser's inconsistent errors, so the same root cause gives the same `error.type` on every browser:

| `error.type` | Meaning and fix |
| - | - |
| `permissions` | The user or the browser blocked device access. `error.blockedBy` is `user` (the user clicked "Block") or `browser` (the browser lacks OS-level access). Unblock in browser or OS settings, then reload. Steps differ by device, so show device-specific instructions where you can. |
| `not-found` | No camera or microphone was found. `error.missingMedia` lists which. The device is unplugged, disabled, or missing. Ask the user to check it and reload. |
| `cam-in-use`, `mic-in-use`, `cam-mic-in-use` | Another app (often Zoom or Teams) holds the device. Windows only. Ask the user to quit the other app and reload. |
| `constraints` | `getUserMedia()` was called with invalid or empty constraints. This is a bug in the app code, not a user problem. Check the device constraints your app passes. |
| `undefined-mediadevices` | `navigator.mediaDevices` is undefined. Almost always the page is served over `http`, not `https`. Browsers only expose media devices on secure origins. |
| `unknown` | Uncategorized. The original browser error is in `error.msg`. A common one is `NotReadableError`, which usually means the OS or a driver blocked the device. |

The [device permissions guide](/docs/daily-js/guides/device-permissions) covers these errors and how to build a UI that helps users recover.

<Warning>
  **Don't call `getUserMedia()` yourself to get the camera or mic.** daily-js calls it internally to handle cross-browser edge cases and to log the call in the session logs that make debugging possible. Calling it yourself causes microphone and camera problems for users. Let daily-js manage devices: select them with [`setInputDevicesAsync({ audioDeviceId, videoDeviceId })`](/reference/daily-js/instance-methods/set-input-devices-async), and control them with [`startCamera()`](/reference/daily-js/instance-methods/start-camera), [`setLocalAudio()`](/reference/daily-js/instance-methods/set-local-audio), and [`setLocalVideo()`](/reference/daily-js/instance-methods/set-local-video). Passing your own `MediaStreamTrack` as `audioSource` or `videoSource` is for custom processing only, and your app then owns that track's lifecycle.
</Warning>

## "I couldn't hear or see the other person"

First find out whether the problem is on their end, your end, or the path between. The dashboard lets you compare both participants in the same session.

* **Check the sender first.** Open the *other* person's participant page. Was their mic or camera on? Did their charts show packet loss or `videoQualityLimReason` entries? If the sender's uplink struggled, everyone in the call had the same problem with them.
* **Then check the receiver.** If the sender looks clean, open the reporter's page. High receive-side packet loss means their downlink: usually wifi or their ISP.
* **Watch for one-way patterns.** Media flowing one way but not the other points at a network path or firewall on one side, not a device.

In your app, [`network-quality-change`](/reference/daily-js/events/network-events#network-quality-change) and [`getNetworkStats()`](/reference/daily-js/instance-methods/get-network-stats) expose the same signals live. `networkStateReasons` says whether send packet loss, receive packet loss, round-trip time, or available bandwidth is the problem.

Non-network causes to rule out:

* The sender's mic was muted, or captured silence because the wrong input device was selected.
* A Bluetooth headset switched profiles mid-call, which can drop audio or make it muffled.
* The sender's device was under heavy CPU load. Look for `cpu load state change` in their event log, or listen for [`cpu-load-change`](/reference/daily-js/events/network-events#cpu-load-change) in your app.

## "They couldn't hear or see me"

Same problem, opposite direction. Now *you* are the sender, so check your own side first:

* Confirm your mic was live. [`startLocalAudioLevelObserver()`](/reference/daily-js/instance-methods/start-local-audio-level-observer) emits [`local-audio-level`](/reference/daily-js/events/media-events#local-audio-level) events you can use for a mic level meter, so users can see their mic working before and during a call.
* Check your participant page for send-side packet loss and `videoQualityLimReason` entries. `bandwidth` means your uplink couldn't keep up. `cpu` means your device couldn't.
* Wifi upload speeds are often far worse than download speeds. A user with "fast internet" can still have a weak uplink. A wired connection is the quickest test.

## "I got disconnected"

Every call uses two connections: `signaling` (call management) and `sfu` (audio and video). [`network-connection`](/reference/daily-js/events/network-events#network-connection) fires when either one is interrupted or recovers.

* An `sfu` interruption pauses media and reconnects on its own. Users see a freeze, then recovery.
* A `signaling` interruption is more serious. The client retries twice, and if both retries fail the call ends with an [`error`](/reference/daily-js/events/error-events#error) event (`type: 'connection-error'`) followed by [`left-meeting`](/reference/daily-js/events/lifecycle-events#left-meeting). There is no fixed deadline: how long the retries take depends on how the connection fails. Separately, the server drops a participant it has heard nothing from for about 35-40 seconds, so others may still see them during a long interruption.

In the dashboard event log, `wss is stale` lines and `network-connection` interrupted events mark these drops. Repeated drops over a short period mean an unstable network or an overloaded CPU. Clean packet loss charts plus repeated interruptions point at CPU. The [logging and metrics guide](/docs/guides/architecture-and-monitoring/logging-and-metrics#network-disruptions) shows how to tell the two apart.

## Capture evidence while it's happening

If you can reproduce the problem, capture two things first.

**The browser console.** Save the full console from the affected user's browser, including red errors and the messages around them.

For far richer console output, turn on daily-js debug logging. Run this in the console, then reload the page:

```javascript theme={null}
localStorage.setItem('debug', 'daily*');
```

daily-js then logs its internal activity to the console for every call on that page. To turn it off, run `localStorage.removeItem('debug')`.

**A screen recording with a visible clock.** A recording that shows the problem (the frozen tile, the error message) next to a wall-clock time lets you line it up with the dashboard event log.

Then gather the rest of the [pre-support checklist](/docs/guides/troubleshooting/before-contacting-support) so a support ticket, if you need one, gets answered in one round trip.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.