Add ability to joining a voice room while it's in progress

This commit is contained in:
mplorentz
2026-03-03 08:54:42 -05:00
committed by hodlbod
parent 9bd57b0caa
commit 559df4b948
2 changed files with 59 additions and 12 deletions
+21 -7
View File
@@ -6,7 +6,12 @@
import ProfileCircle from "@app/components/ProfileCircle.svelte"
import RoomName from "@app/components/RoomName.svelte"
import {pushToast} from "@app/util/toast"
import {deriveVoiceParticipants, joinVoiceRoom, currentVoiceSession} from "@app/voice"
import {
deriveVoiceParticipants,
joinVoiceRoom,
leaveVoiceRoom,
currentVoiceSession,
} from "@app/voice"
interface Props {
url: string
@@ -18,17 +23,29 @@
const participants = deriveVoiceParticipants(url, h)
const isActive = $derived($currentVoiceSession?.url === url && $currentVoiceSession?.h === h)
let isJoining = $state(false)
let joinAbortController: AbortController | undefined
const handleClick = async () => {
if (isJoining) return
if (isActive) {
await leaveVoiceRoom()
return
}
if (isJoining) {
joinAbortController?.abort()
return
}
joinAbortController = new AbortController()
isJoining = true
try {
await joinVoiceRoom(url, h)
await joinVoiceRoom(url, h, joinAbortController.signal)
} catch (e) {
if (e instanceof Error && e.message === "Join cancelled") return
if (e instanceof DOMException && e.name === "AbortError") return
const message = e instanceof Error ? e.message : String(e)
pushToast({theme: "error", message: `Failed to join voice room: ${message}`})
} finally {
isJoining = false
joinAbortController = undefined
}
}
@@ -40,10 +57,7 @@
</script>
<div>
<SecondaryNavItem
onclick={handleClick}
disabled={isJoining}
class={isActive ? "!bg-base-100 !text-base-content" : ""}>
<SecondaryNavItem onclick={handleClick} class={isActive ? "!bg-base-100 !text-base-content" : ""}>
{#if isJoining}
<span class="loading loading-spinner loading-sm"></span>
{:else}
+38 -5
View File
@@ -24,6 +24,7 @@ export const currentVoiceSession = writable<VoiceSession | undefined>(undefined)
const fetchLivekitToken = async (
url: string,
groupId: string,
signal?: AbortSignal,
): Promise<{server_url: string; participant_token: string}> => {
const httpUrl = url
.replace(/^wss:\/\//, "https://")
@@ -34,6 +35,8 @@ const fetchLivekitToken = async (
const $signer = signer.get()
if (!$signer) throw new Error("No signer available")
if (signal?.aborted) throw new Error("Join cancelled")
const authHeader = await getToken(
endpoint,
"GET",
@@ -47,9 +50,16 @@ const fetchLivekitToken = async (
true,
)
const response = await fetch(endpoint, {
headers: {Authorization: authHeader},
})
let response: Response
try {
response = await fetch(endpoint, {
headers: {Authorization: authHeader},
signal,
})
} catch (e) {
if (e instanceof DOMException && e.name === "AbortError") throw new Error("Join cancelled")
throw e
}
if (!response.ok) {
const text = await response.text()
@@ -104,7 +114,11 @@ const stopPresenceHeartbeat = () => {
}
}
export const joinVoiceRoom = async (url: string, h: string) => {
export const joinVoiceRoom = async (
url: string,
h: string,
signal?: AbortSignal,
): Promise<void> => {
const session = get(currentVoiceSession)
if (session) {
@@ -112,7 +126,9 @@ export const joinVoiceRoom = async (url: string, h: string) => {
await leaveVoiceRoom()
}
const {server_url, participant_token} = await fetchLivekitToken(url, h)
const {server_url, participant_token} = await fetchLivekitToken(url, h, signal)
if (signal?.aborted) throw new Error("Join cancelled")
const room = new Room({
adaptiveStream: true,
@@ -144,6 +160,17 @@ export const joinVoiceRoom = async (url: string, h: string) => {
track.detach().forEach(el => el.remove())
})
const onAbort = () => {
room.disconnect()
}
if (signal) {
if (signal.aborted) {
room.disconnect()
throw new Error("Join cancelled")
}
signal.addEventListener("abort", onAbort, {once: true})
}
const CONNECT_TIMEOUT_MS = 5_000
try {
@@ -158,8 +185,14 @@ export const joinVoiceRoom = async (url: string, h: string) => {
])
} catch (e) {
room.disconnect()
if (signal?.aborted) {
throw new Error("Join cancelled")
}
throw e
} finally {
signal?.removeEventListener("abort", onAbort)
}
if (signal?.aborted) throw new Error("Join cancelled")
await room.localParticipant.setMicrophoneEnabled(true)
currentVoiceSession.set({url, h, room, muted: false})