Header Logo

Troubleshooting WebRTC Drops in a Svelte AI Interview Practice Bot

Troubleshooting WebRTC Drops in a Svelte AI Interview Practice Bot

Debug Svelte WebRTC drops in interview bots: isolate signaling, ICE, autoplay, and lifecycle bugs with browser stats and reconnection logic.

Introduction


If your Svelte interview practice bot works locally but the face freezes, audio keeps going, or the entire session drops after a few minutes, you are almost always debugging a WebRTC problem rather than a “Svelte problem.” The browser app is just the signaling and rendering layer. The real failure modes live in ICE negotiation, NAT traversal, media track lifecycle, autoplay policy, reconnection behavior, and how your app reacts when the backend rotates sessions or the peer connection is torn down.


This post walks through the failure modes I see most often in realtime avatar apps, how to isolate each one, and how to instrument a Svelte client so you can tell whether the bug is in your UI, your signaling flow, or the media path itself. By the end, you should be able to reproduce drops deterministically, inspect the right browser metrics, and harden the bot so it recovers instead of silently dying.


Start with the failure boundary: signaling, media, or UI


Before touching code, classify the drop. WebRTC sessions usually fail in one of three places:


  • Signaling failure: the client never completes SDP exchange, or it completes once and never refreshes credentials.

  • Transport failure: ICE connectivity succeeds initially, then goes to disconnected/failed because the selected candidate pair becomes unusable.

  • Application failure: the peer connection stays up, but your Svelte component tears down the media element, unsubscribes from tracks, or triggers a remount that resets local state.


In practice, people blame WebRTC when the bug is often a Svelte lifecycle issue. If your component conditionally renders the video element with {#if sessionActive}, then toggling that flag can destroy the element, detach tracks, and make the avatar appear “dropped” even though the peer connection is still alive. Likewise, if you recreate the RTCPeerConnection or LiveKit room on every reactive update, you can trigger reconnect loops that look like network instability.


So the first rule is simple: keep the media objects stable, and let state changes update props, not the underlying connection.


Instrument the browser like a networking tool


When a session drops, the browser already knows why. You just need to ask it. Two things matter most: connection state transitions and ICE candidate pair statistics.


At minimum, log these events from your WebRTC layer:


pc.onconnectionstatechange = () => {

};
pc.onconnectionstatechange = () => {

};
pc.onconnectionstatechange = () => {

};


For a healthy session, you generally expect:


  • newconnectingconnected

  • Occasional brief disconnected states on unstable networks, followed by recovery

  • Rarely, a transition to failed, which usually means the connection is unrecoverable without re-joining


Then inspect stats every few seconds when debugging:


const stats = await pc.getStats();
}
const stats = await pc.getStats();
}
const stats = await pc.getStats();
}


Useful signals:


  • RTT spikes often correlate with Wi-Fi contention, VPNs, or aggressive packet loss.

  • Bitrate collapses can cause the video to freeze while the audio limps along.

  • Bytes stop moving while state remains connected can indicate a track delivery problem or app-level mute/unsubscribe logic.


Also check the browser console for autoplay errors. On first load, if the avatar video or synthesized audio is blocked until the user interacts with the page, you can mistake a muted or paused element for a connection failure.


Common Svelte-specific mistakes that masquerade as WebRTC drops


1. Recreating the session on every reactive change


Svelte makes it easy to write code that reruns when state changes. That is good for UI, bad for realtime connections if the connection setup lives in a reactive block. If you initialize the room or peer connection from a $: statement that depends on props or local state, you may be tearing down and rebuilding the media stack on every render.


Prefer an explicit onMount initialization and a matching cleanup:


import { onMount } from 'svelte';

});
import { onMount } from 'svelte';

});
import { onMount } from 'svelte';

});


Keep derived state separate from connection lifecycle. UI changes should not cause a reconnect unless you intentionally want a new session.


2. Unmounting the video element


When a remote track is attached to a DOM element, removing that element can make the avatar disappear even if the track is still live. In Svelte, this often happens when the avatar component is nested inside conditional rendering keyed by conversation state.


Safer pattern: render the media container once, then update its contents. If you need to toggle visibility, use CSS rather than destroying the node.


3. Ignoring cleanup for old tracks


If a session reconnects, old tracks may still be attached to a stale element or stale state object. That can create duplicate audio playback, “ghost” video frames, or memory leaks that only appear after several retries. Clear event listeners and detach tracks when a session ends.


4. Confusing backend session expiry with WebRTC failure


For interview bots, the UI often runs against a session token that expires or is invalidated when the backend decides the conversation is over. The frontend sees a disconnect, but the root cause is an application-level session end. Treat “room closed by server” differently from network loss. The recovery logic should not blindly auto-rejoin if the backend intentionally ended the session.


What to inspect when the bot drops after a few minutes


If the failure is time-based, look for one of these patterns:


  1. STUN/TURN path instability: a relay candidate was selected, then the relay became overloaded or blocked.

  2. Token/session expiry: the backend session or access token timed out.

  3. Tab suspension: background throttling in the browser delayed timers enough to break heartbeats or UI logic.

  4. Mobile network handoff: switching Wi-Fi to LTE forces ICE to renegotiate or fail entirely.


For long-lived interview practice sessions, you should assume network path changes are normal. Implement reconnect UX explicitly: show “reconnecting” after a short grace period, preserve transcript state independently of media state, and only destroy the session after you know it cannot be recovered.


A good rule is to separate three clocks:


  • Conversation clock: transcript, scoring, interview timer

  • Media clock: peer connection and track attachments

  • Backend clock: session lease or auth token validity


If those are coupled too tightly, a transient network glitch becomes a lost interview.


How to harden reconnection logic


When iceConnectionState becomes disconnected, do not immediately reinitialize everything. Give the browser a brief window to recover, because short disruptions are common. If it transitions to failed, then re-create the session from scratch.


That usually looks like:


  1. Mark the UI as reconnecting.

  2. Preserve user-facing state: transcript, question index, timer, and any interview rubric.

  3. Attempt a fresh join with a new session or fresh signaling credentials.

  4. Re-attach the remote avatar track once the new peer connection is established.


Do not assume the old media tracks are reusable after a failure. In WebRTC, a dead peer connection is usually dead for good.


Where Protoface fits in this stack


Protoface is useful here because it gives you a consistent avatar/session layer while you keep your own app logic focused on the interview workflow. For browser-based bots, the customer-managed iframe embed is the lowest-friction option: you avoid exposing any API key in the browser, and the embed handles the realtime avatar surface for you. That removes a lot of accidental complexity from the client side, especially if your issue is “the face dropped” rather than “my entire voice agent is broken.”


If you are instead wiring a voice agent in Python, the LiveKit plugin path is often the right integration point. The point is not that the plugin magically fixes WebRTC; it is that it keeps avatar attachment and session management in one place, which makes it easier to distinguish avatar/session problems from your own Svelte UI bugs. See the relevant docs and examples in the documentation and the integration repositories on GitHub when you need exact setup details.


For REST-driven workflows, the API is the place to create or inspect sessions and avatars from your backend. That is especially useful if you want your Svelte app to consume a short-lived session reference rather than directly managing long-lived credentials in the client.


A practical debugging checklist


  • Verify the video/audio elements are not being conditionally destroyed by Svelte.

  • Log connectionState and iceConnectionState changes.

  • Inspect candidate-pair stats for RTT, bitrate, and byte counters.

  • Confirm the session is not expiring on the backend at the same time as the drop.

  • Test on a different network, with VPN off, and in an incognito window to rule out local browser state.

  • Distinguish “remote track stopped” from “UI removed the element.” They are not the same bug.


If the problem disappears in one browser and not another, compare autoplay policy, hardware acceleration, and tab suspension behavior before blaming your signaling stack.


Conclusion


WebRTC drops in a Svelte interview bot are usually diagnosable if you separate application state from media state and inspect the browser’s connection metrics instead of guessing. Start by identifying whether the failure is signaling, transport, or UI lifecycle. Then harden your component so it initializes once, preserves media elements across reactive updates, and handles reconnects deliberately.


If you want a cleaner avatar/session boundary while you debug your own app logic, use the relevant Protoface integration surface for your architecture and keep the exact API fields and setup details close to the docs at docs.protoface.com. That will save you time the next time a “video drop” turns out to be a lifecycle bug in disguise.

Add a face to your AI.

No credit card needed.

Add a face to your AI.

No credit card needed.

Add a face to your AI.

No credit card needed.