In this guide you'll make your call handle network drops well.
Real users lose their network all the time: they walk out of Wi-Fi range, ride through a tunnel, switch from Wi-Fi to mobile data, or close a laptop lid. The Livqeno SDK reconnects on its own. Your job is the UI: telling the user what's happening, and not making a short blip look like the call ended.
Where this fits: you have a working call. This guide doesn't add a feature; it makes the one you have survive real networks. Do it before launch.
What you'll build
A call screen that, when the network drops:
- Tells the user what's happening.
- Keeps the video grid on screen.
- Recovers on its own, without the user pressing anything.
What you need
- A working call. See Build a video call.
Steps
1. Show the connection state
The SDK tracks the connection and tells you its state. Show it in your app; don't try to guess it from other signals. These are the five states:
type ConnectionState = 'connecting' | 'connected' | 'reconnecting' | 'disconnected' | 'failed';room.on('connectionStateChanged', (state) => {
banner.textContent = {
connecting: 'Connecting…',
connected: '',
reconnecting: 'Connection lost — reconnecting…',
disconnected: 'You have left the call.',
failed: 'Could not reconnect.',
}[state];
});import { useConnectionState } from '@livqeno/react';
function Banner() {
const state = useConnectionState();
if (state === 'connected') return null;
if (state === 'reconnecting') return <p role="status">Connection lost — reconnecting…</p>;
if (state === 'failed') return <p role="alert">Could not reconnect.</p>;
return <p role="status">Connecting…</p>;
}room.connectionStateChanges.listen((state) {
setState(() => _state = state);
});You should now see "Connection lost — reconnecting…" when you turn off Wi-Fi, and the banner clear when you turn it back on.
2. Keep the grid on screen
A common mistake: clearing the list of people when the state is
reconnecting. During a reconnect, Livqeno keeps everyone in the room, and
their tiles come back by themselves. If your grid empties and refills, a
two-second blip looks to the user like everyone left the call.
// Do this
{state === 'reconnecting' && <ReconnectingOverlay />}
<ParticipantGrid /> {/* stays mounted */}
// Not this
{state === 'connected' ? <ParticipantGrid /> : <Spinner />}3. Decide what to do on failed
failed means the SDK tried several times and gave up. The usual cause is
a long outage. What happens next is up to you. First check why the room
disconnected: if your backend removed the participant (REMOVED) or closed
the room (ROOM_CLOSED), don't rejoin. The SDK never reconnects in those
cases, and your app shouldn't either.
room.on('disconnected', (reason) => {
if (reason === 'REMOVED' || reason === 'ROOM_CLOSED') showCallEnded(reason);
});For the rest, a good default is to rejoin automatically with a new token from your backend:
room.on('connectionStateChanged', async (state) => {
if (state !== 'failed') return;
// A fresh token, because the old one may have expired while offline.
const credentials = await fetch('/join-room', { method: 'POST' }).then((r) => r.json());
const client = createRTCClient(credentials);
const rejoined = await client.join(credentials.roomId);
wireUp(rejoined);
});Pass refreshToken and the SDK renews the token on its own, during the
call and before a reconnect that would present an expired one, on every
SDK: createRTCClient({ …, refreshToken }), new Livqeno({ …, refreshToken })
on React Native, Livqeno(refreshToken: …) on Flutter. See
Keep long calls alive. You still
need the rejoin above for a failed room.
4. Wait for the connection when you need it
join() gives you a room object quickly, but the media connection may still
be starting. If your next step needs a live connection, wait for it:
const room = await client.join(roomId);
await room.waitUntilConnected(15_000); // resolves on connect, rejects on timeout
await room.enableCamera();If it doesn't connect within 15 seconds, the promise rejects, and you can show an error instead of a spinner that never ends.
5. Show connection quality
A reconnect is often preceded by a bad patch. Measuring packet loss (the share of media that didn't arrive) lets you warn the user early, for example "Your connection is unstable":
setInterval(async () => {
const stats = await room.getConnectionStats();
const worst = Math.max(0, ...stats.remote.map((t) => t.packetLossPercent ?? 0));
setQuality(worst > 10 ? 'poor' : worst > 3 ? 'fair' : 'good');
}, 5000);Warn users before the call drops.
Use the per-track numbers above, which are real measurements. Don't rely on
stats.connectionQuality: today it usually says 'unknown', because the
server doesn't compute it yet.
How it works
What happens when the network drops:
- Reconnecting is automatic and on by default (
autoReconnect: true). The SDK retries with exponential backoff (it waits a little longer between each try, so thousands of clients don't all retry at once). - A short drop is resumed, not rebuilt. Each retry asks the server to resume the previous session. Within about two minutes the media server still has it, so media keeps flowing and nothing is renegotiated. After that, the SDK builds a new media connection and sends your camera and mic again.
- A media-only failure is recovered too. If the media connection fails
while signaling is fine, the SDK rejoins with a new media connection on
its own, and you see
reconnectingthenreconnected. - Your tracks come back on their own. You don't need to turn the camera
on again. That's why the
reconnectedevent has no data. - The states are real.
reconnectingmeans the connection really dropped. The SDK also receives the media server's own view of the connection, sogetDiagnostics()may show "server says failed, browser says connected". That's useful information when debugging, not a bug. - A token expiring during a call only ends it where the server enforces
expiry. The server warns the SDK a minute before; with
refreshTokenthe SDK renews the token in place. Without it you get aTOKEN_EXPIRINGwarning, and, if enforcement is on, adisconnectedwith reasonTOKEN_EXPIREDat expiry.
Production tips
- TURN makes reconnecting work on real networks. A TURN relay passes
media along when a network blocks a direct connection. Switching from Wi-Fi
to cellular usually needs one. Livqeno includes TURN details in every token
grant, in
iceServers. - Use
iceServersfrom a new token. TURN credentials expire with the token. Reusing an oldiceServerslist when rejoining is a common, confusing failure. - Our deploys close connections on purpose. Clients reconnect to another
server within two seconds and resume their sessions, so media usually keeps
flowing. To your users, a deploy looks like a brief
reconnectingbanner at most. - Test it for real. Turn Wi-Fi off for ten seconds. Reloading the page, or Chrome DevTools' offline toggle, doesn't test the same thing.
Next steps
- Reconnection & network quality: details for each SDK.
- Build for production: the rest of what to do before launch.
- Diagnostics · Troubleshooting