LiveKit is a genuinely solid open-source WebRTC platform, used here as the voice/video backend for a self-hosted Discord-style chat app across web, desktop, Android, and iOS clients. These are three real, non-obvious bugs hit shipping that across every platform — one a WebRTC-spec subtlety, one a plain React state race, and one that only ever showed up natively on iOS and needed reading the SDK's actual source to root-cause.
MediaStreamTrack entirely. If you're running your own voice-activity detection (an
AnalyserNode reading the published track directly, to drive a "someone's talking" UI indicator
or a push-to-talk gate), building that analyser directly on the track LiveKit manages means it goes
permanently silent the moment mute closes the gate — and never comes back, even after unmuting, because the
track it was reading has been stopped, not just muted.
Fix: build your voice-activity analyser on a MediaStreamTrack.clone() of the
published track instead of the track itself. Per the WebRTC spec, a cloned track has an independent
enabled/lifecycle state from its source — muting the original through LiveKit's own API doesn't touch the
clone, so your analyser keeps working across mute cycles indefinitely. This also avoids triggering a second
microphone permission prompt, since you're cloning an already-granted track rather than opening a new one.
true if nothing
explicitly resets it along every path a room can end up disconnected (a clean leave and a connection drop
are different code paths; both need to clear it).
Fix: give every room instance an identity check — an isCurrent() guard called
at the top of every listener callback, checking whether the room this event belongs to is still the one the
UI thinks is active, and bailing out immediately if not. Separately, explicitly reset any "connecting"/
loading state on every path a room can end (a clean disconnect event and an explicit "leave"
action both need the same cleanup, not just one of them). This class of bug is easy to miss in testing
because it only shows up under fast, repeated channel switching — a single deliberate join/leave test won't
reproduce it.
This is the sharpest one: a native iOS app using LiveKit's Swift SDK could reliably fail to connect to
voice, timing out with a generic network/validation error — while the exact same account, on the exact same
device and network, connected instantly over Safari using the JS SDK. Every server-side check came back
clean: the signaling endpoint responded correctly to a direct curl, TLS was fine, there was no
stale session state, disabling HTTP/3 as a test made no difference.
curl nor a browser-based JS
client ever exercises this code path at all, which is exactly why every server-side and cross-platform
check came back clean — the problem was entirely inside one specific client SDK's own networking layer.
This matched a known, still-open issue in LiveKit's own issue tracker — a maintainer had already suspected
"multipath handling with certain network providers," and a one-line fix had been proposed and discussed,
but closed unmerged for lack of a clean reproduction. Confirming it independently here was enough to trust
the fix: applied via a pinned fork forcing multipathServiceType from .handover to
.none, rather than waiting on it to land upstream.
The general lesson: when a bug is reproducible on exactly one client platform and nowhere else — not the server, not other clients on the same account — the SDK's own platform-specific networking layer is a real suspect, not just app-level code. Reading the actual SDK source for the platform in question found this in an afternoon; guessing at server-side causes for it would never have gotten there.