Fix call mute failures, stale session writes, and leaked audio elements

This commit is contained in:
Jon Staab 2026-07-27 15:01:48 -07:00
parent eae644ab3c
commit fe90a9f0ba

View file

@ -214,9 +214,7 @@ export const joinVoiceRoom = async (
whenAborted(signal), whenAborted(signal),
]) ])
} catch (e) { } catch (e) {
if (activeRoom === liveKitRoom) activeRoom = undefined teardownRoom(liveKitRoom)
liveKitRoom.removeAllListeners()
liveKitRoom.disconnect()
throw e throw e
} }
@ -239,9 +237,7 @@ export const joinVoiceRoom = async (
// A cancel during the mic step must tear down the connected room rather // A cancel during the mic step must tear down the connected room rather
// than leaking it. // than leaking it.
if (signal.aborted) { if (signal.aborted) {
if (activeRoom === liveKitRoom) activeRoom = undefined teardownRoom(liveKitRoom)
liveKitRoom.removeAllListeners()
liveKitRoom.disconnect()
throw new AbortError() throw new AbortError()
} }
@ -294,9 +290,7 @@ export const leaveVoiceRoom = async () => {
} }
// Always tear down this room's connection and listeners. // Always tear down this room's connection and listeners.
if (activeRoom === session.livekit) activeRoom = undefined teardownRoom(session.livekit)
session.livekit.removeAllListeners()
session.livekit.disconnect()
// Only reset shared UI state if this session is still current. A slow leave // Only reset shared UI state if this session is still current. A slow leave
// that was superseded by a new join (bounded by a timeout in joinVoiceRoom) // that was superseded by a new join (bounded by a timeout in joinVoiceRoom)
@ -320,16 +314,16 @@ export const toggleMute = async () => {
if (!session) return if (!session) return
callMicMuted.update(not) callMicMuted.update(not)
if (get(callMicMuted)) {
session.livekit.localParticipant.setMicrophoneEnabled(false)
return
}
const muted = get(callMicMuted)
try { try {
await session.livekit.localParticipant.setMicrophoneEnabled(true) await session.livekit.localParticipant.setMicrophoneEnabled(!muted)
} catch (e) { } catch {
callMicMuted.set(true) callMicMuted.set(!muted)
pushToast({theme: "error", message: "Could not access microphone"}) pushToast({
theme: "error",
message: muted ? "Could not mute microphone" : "Could not access microphone",
})
} }
} }
@ -340,7 +334,7 @@ export const toggleCamera = async () => {
const cameraOn = !session.cameraOn const cameraOn = !session.cameraOn
try { try {
await session.livekit.localParticipant.setCameraEnabled(cameraOn) await session.livekit.localParticipant.setCameraEnabled(cameraOn)
currentCallSession.set({...session, cameraOn}) currentCallSession.update(s => s && {...s, cameraOn})
} catch { } catch {
pushToast({ pushToast({
theme: "error", theme: "error",
@ -356,7 +350,7 @@ export const toggleScreenShare = async () => {
const screenShareOn = !session.screenShareOn const screenShareOn = !session.screenShareOn
try { try {
await session.livekit.localParticipant.setScreenShareEnabled(screenShareOn) await session.livekit.localParticipant.setScreenShareEnabled(screenShareOn)
currentCallSession.set({...session, screenShareOn}) currentCallSession.update(s => s && {...s, screenShareOn})
} catch { } catch {
pushToast({ pushToast({
theme: "error", theme: "error",
@ -463,6 +457,21 @@ currentCallSession.subscribe(session => {
resetVideoCallLayout() resetVideoCallLayout()
}) })
const teardownRoom = (livekit: LiveKitRoom) => {
if (activeRoom === livekit) activeRoom = undefined
// Dropping the listeners keeps TrackUnsubscribed from removing the hidden
// audio elements onTrackSubscribed appended, so detach them here instead.
for (const participant of livekit.remoteParticipants.values()) {
for (const publication of participant.audioTrackPublications.values()) {
publication.track?.detach().forEach(el => el.remove())
}
}
livekit.removeAllListeners()
livekit.disconnect()
}
const countLiveVisualFeeds = (session: CallSession): number => { const countLiveVisualFeeds = (session: CallSession): number => {
const livekit = session.livekit const livekit = session.livekit
let n = 0 let n = 0
@ -639,6 +648,8 @@ const makeOnRoomDisconnected = (livekit: LiveKitRoom) => (reason?: DisconnectRea
// Ignore disconnects from rooms that are no longer the active session. // Ignore disconnects from rooms that are no longer the active session.
if (livekit !== activeRoom) return if (livekit !== activeRoom) return
// Livekit unsubscribes remote tracks before emitting Disconnected, so
// onTrackUnsubscribed has already removed their audio elements by now.
activeRoom = undefined activeRoom = undefined
livekit.removeAllListeners() livekit.removeAllListeners()