LiveKit is a genuinely solid open-source WebRTC platform. I use it as the voice and video backend for a self-hosted Discord-style chat app, across web, desktop, Android and iOS clients. These are three real, non-obvious bugs I hit shipping that on every platform. One is a WebRTC-spec subtlety. One is a plain React state race. One only ever showed up natively on iOS, and needed reading the SDK's source to root-cause.
MediaStreamTrack entirely. Say you run your own voice-activity
detection. An AnalyserNode reading the published track directly, driving a "someone's talking"
indicator or a push-to-talk gate. Build that analyser on the track LiveKit manages and it goes permanently
silent the moment mute closes the gate. It never comes back, not even after unmuting, because the track it
was reading has been stopped rather than muted.
Fix: build your voice-activity analyser on a MediaStreamTrack.clone() of the
published track, not the track itself. Per the WebRTC spec a cloned track has an independent enabled and
lifecycle state from its source. Muting the original through LiveKit's API does not touch the clone, so your
analyser keeps working across mute cycles indefinitely. It also avoids a second microphone permission prompt,
since you are cloning an already-granted track rather than opening a new one.
true if nothing 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 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 if not. Separately, reset any connecting or 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. It only shows up under fast, repeated channel
switching. A single deliberate join and leave will not reproduce it.
This is the sharpest one. A native iOS app using LiveKit's Swift SDK reliably failed to connect to voice,
timing out with a generic network error. The same account, on the 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 exercises that code path at all. Which is
exactly why every server-side and cross-platform check came back clean. The problem lived entirely inside one
client SDK's networking layer.
This matched a known, still-open issue in LiveKit's own tracker. A maintainer had already suspected
"multipath handling with certain network providers". A one-line fix had been proposed and discussed, then
closed unmerged for lack of a clean reproduction. Confirming it independently here was enough to trust the
fix. I applied it via a pinned fork forcing multipathServiceType from .handover to
.none, instead of waiting for it to land upstream.
The general lesson. When a bug reproduces on exactly one client platform and nowhere else, not the server and not other clients on the same account, the SDK's own platform-specific networking layer is a real suspect. Not just your app code. Reading the SDK source for that platform found this in an afternoon. Guessing at server-side causes would never have gotten there.