When a user says "the call was bad", you need numbers to find out why: was it their network, their device, or something else? This page shows how to check a call's health and quality. Use it to:
- show a quality badge in your call screen,
- attach details to a bug report or support ticket,
- spot users who keep having bad calls.
All code runs in your app. You'll see some network words; here's what they mean:
| Word | Meaning | When it's bad |
|---|---|---|
| RTT (round-trip time) | How long a packet takes to reach the server and come back. | High RTT feels like lag: people talk over each other. |
| Jitter | How uneven packet arrival is. | High jitter sounds choppy or robotic. |
| Packet loss | Share of packets that never arrived. | Above a few percent, video freezes and audio breaks up. |
| Bitrate | How much data per second a track uses. | Low bitrate on video means blurry video. |
| Codec | The format video is compressed in (e.g. VP8, H.264). | Useful when only some devices have problems. |
| ICE | How the device finds a network path to the media server. | failed means no path was found, often a firewall. |
There are two methods:
| Method | Cost | Use it for |
|---|---|---|
getDiagnostics() | Cheap, instant | Connection state, for bug reports |
getConnectionStats() | Does real work, async | Media quality numbers (loss, jitter, bitrate) |
Both work the same on Web and React Native (it's the same Room class).
getDiagnostics() — quick connection info
const info = room.getDiagnostics();
// {
// connectionState, reconnectCount, sdkVersion, platform, browser,
// iceConnectionState, signalingState,
// remoteIceConnectionState, remotePeerConnectionState,
// }You can call it any time. It never contains a token or a secret, so you can attach it to a bug report as it is.
| Field | What it tells you |
|---|---|
connectionState | Livqeno's connection state. Use this one for your UI. |
reconnectCount | How many times the room reconnected on its own. |
iceConnectionState | The network (ICE) state, as your device sees it. |
signalingState | The browser's signaling state. |
remoteIceConnectionState | The network (ICE) state, as the media server sees it. |
remotePeerConnectionState | The connection state, as the media server sees it. |
sdkVersion, platform, browser | Your setup, for a bug report. |
The local and remote values can disagree. That's useful: "server says failed, browser says connected" tells you where the problem is.
getConnectionStats() — call quality numbers
const stats = await room.getConnectionStats();
// {
// connectionState,
// connectionQuality, // 'excellent' | 'good' | 'poor' | 'lost' | 'unknown' — the SFU's own read
// local: TrackStats[], // one entry per track you've published
// remote: TrackStats[], // one entry per track you've subscribed to
// }Each TrackStats entry looks like this:
{
kind, direction, // 'send' | 'receive'
bitrateBps, // undefined on a track's very first sample — there's nothing yet to diff against
packetsLost, packetLossPercent,
jitterMs,
roundTripTimeMs, // only ever present on a send-direction track — WebRTC never reports a receiver's own RTT
codec, // only ever present on a receive-direction video track
frameWidth, frameHeight, framesPerSecond,
}This call does real work for every track. Call it every few seconds, not in a tight loop:
setInterval(async () => {
const stats = await room.getConnectionStats();
console.log('quality:', stats.connectionQuality);
for (const track of stats.remote) {
if (track.packetLossPercent && track.packetLossPercent > 5) {
console.warn(`${track.kind} losing packets: ${track.packetLossPercent.toFixed(1)}%`);
}
}
}, 5000);On React Native, it's the same call. room is what livqeno.join() returned:
const stats = await room.getConnectionStats();See stats on the server
You won't always have the user's device in front of you. That's why the same numbers are also sent to Livqeno, where your team can look them up later from a support ticket.
If your SDK version sends stats as telemetry, the numbers show up in
Livqeno's Connection records automatically. No extra code needed.
View them in the dashboard, or with livqeno connections inspect <id>.
When there are several tracks, they're combined into one number per field:
| Field | How it's combined |
|---|---|
| RTT | From a track you send |
| Jitter, packet loss | The worst value across all tracks |
| Bitrate | Added up across all tracks |
This works with every client SDK, including Flutter.
Common errors
| Error | Cause | Fix |
|---|---|---|
getConnectionStats is not a function (Flutter) | Flutter doesn't have it yet. | Use room.connectionStateChanges for state, and server-side stats (above) for quality. |
Every TrackStats.bitrateBps is undefined | It's the first sample after a track started. | Wait for the second sample. |
Tips for production
- Check stats on a timer (every 5 seconds is a good default). Never in a render loop.
- Use
connectionQualityas the main answer. It's pushed to you: listen forconnectionQualityChanged(or readparticipant.connectionQuality) rather than polling for it. Only look at the rawTrackStatsfields when you need to explain why quality is poor. - To check a device and network before a call, run the pre-call test.
- Diagnostics never include a token, a secret, or personal data. They're safe to log or send to support.
Next steps
- Reconnection: show a quality indicator using
connectionQuality. - Troubleshooting: what these numbers mean when something's wrong.