Add ability to joining a voice room while it's in progress
This commit is contained in:
@@ -6,7 +6,12 @@
|
|||||||
import ProfileCircle from "@app/components/ProfileCircle.svelte"
|
import ProfileCircle from "@app/components/ProfileCircle.svelte"
|
||||||
import RoomName from "@app/components/RoomName.svelte"
|
import RoomName from "@app/components/RoomName.svelte"
|
||||||
import {pushToast} from "@app/util/toast"
|
import {pushToast} from "@app/util/toast"
|
||||||
import {deriveVoiceParticipants, joinVoiceRoom, currentVoiceSession} from "@app/voice"
|
import {
|
||||||
|
deriveVoiceParticipants,
|
||||||
|
joinVoiceRoom,
|
||||||
|
leaveVoiceRoom,
|
||||||
|
currentVoiceSession,
|
||||||
|
} from "@app/voice"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
url: string
|
url: string
|
||||||
@@ -18,17 +23,29 @@
|
|||||||
const participants = deriveVoiceParticipants(url, h)
|
const participants = deriveVoiceParticipants(url, h)
|
||||||
const isActive = $derived($currentVoiceSession?.url === url && $currentVoiceSession?.h === h)
|
const isActive = $derived($currentVoiceSession?.url === url && $currentVoiceSession?.h === h)
|
||||||
let isJoining = $state(false)
|
let isJoining = $state(false)
|
||||||
|
let joinAbortController: AbortController | undefined
|
||||||
|
|
||||||
const handleClick = async () => {
|
const handleClick = async () => {
|
||||||
if (isJoining) return
|
if (isActive) {
|
||||||
|
await leaveVoiceRoom()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (isJoining) {
|
||||||
|
joinAbortController?.abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
joinAbortController = new AbortController()
|
||||||
isJoining = true
|
isJoining = true
|
||||||
try {
|
try {
|
||||||
await joinVoiceRoom(url, h)
|
await joinVoiceRoom(url, h, joinAbortController.signal)
|
||||||
} catch (e) {
|
} 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)
|
const message = e instanceof Error ? e.message : String(e)
|
||||||
pushToast({theme: "error", message: `Failed to join voice room: ${message}`})
|
pushToast({theme: "error", message: `Failed to join voice room: ${message}`})
|
||||||
} finally {
|
} finally {
|
||||||
isJoining = false
|
isJoining = false
|
||||||
|
joinAbortController = undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,10 +57,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<SecondaryNavItem
|
<SecondaryNavItem onclick={handleClick} class={isActive ? "!bg-base-100 !text-base-content" : ""}>
|
||||||
onclick={handleClick}
|
|
||||||
disabled={isJoining}
|
|
||||||
class={isActive ? "!bg-base-100 !text-base-content" : ""}>
|
|
||||||
{#if isJoining}
|
{#if isJoining}
|
||||||
<span class="loading loading-spinner loading-sm"></span>
|
<span class="loading loading-spinner loading-sm"></span>
|
||||||
{:else}
|
{:else}
|
||||||
|
|||||||
+38
-5
@@ -24,6 +24,7 @@ export const currentVoiceSession = writable<VoiceSession | undefined>(undefined)
|
|||||||
const fetchLivekitToken = async (
|
const fetchLivekitToken = async (
|
||||||
url: string,
|
url: string,
|
||||||
groupId: string,
|
groupId: string,
|
||||||
|
signal?: AbortSignal,
|
||||||
): Promise<{server_url: string; participant_token: string}> => {
|
): Promise<{server_url: string; participant_token: string}> => {
|
||||||
const httpUrl = url
|
const httpUrl = url
|
||||||
.replace(/^wss:\/\//, "https://")
|
.replace(/^wss:\/\//, "https://")
|
||||||
@@ -34,6 +35,8 @@ const fetchLivekitToken = async (
|
|||||||
const $signer = signer.get()
|
const $signer = signer.get()
|
||||||
if (!$signer) throw new Error("No signer available")
|
if (!$signer) throw new Error("No signer available")
|
||||||
|
|
||||||
|
if (signal?.aborted) throw new Error("Join cancelled")
|
||||||
|
|
||||||
const authHeader = await getToken(
|
const authHeader = await getToken(
|
||||||
endpoint,
|
endpoint,
|
||||||
"GET",
|
"GET",
|
||||||
@@ -47,9 +50,16 @@ const fetchLivekitToken = async (
|
|||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
|
|
||||||
const response = await fetch(endpoint, {
|
let response: Response
|
||||||
headers: {Authorization: authHeader},
|
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) {
|
if (!response.ok) {
|
||||||
const text = await response.text()
|
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)
|
const session = get(currentVoiceSession)
|
||||||
|
|
||||||
if (session) {
|
if (session) {
|
||||||
@@ -112,7 +126,9 @@ export const joinVoiceRoom = async (url: string, h: string) => {
|
|||||||
await leaveVoiceRoom()
|
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({
|
const room = new Room({
|
||||||
adaptiveStream: true,
|
adaptiveStream: true,
|
||||||
@@ -144,6 +160,17 @@ export const joinVoiceRoom = async (url: string, h: string) => {
|
|||||||
track.detach().forEach(el => el.remove())
|
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
|
const CONNECT_TIMEOUT_MS = 5_000
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -158,8 +185,14 @@ export const joinVoiceRoom = async (url: string, h: string) => {
|
|||||||
])
|
])
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
room.disconnect()
|
room.disconnect()
|
||||||
|
if (signal?.aborted) {
|
||||||
|
throw new Error("Join cancelled")
|
||||||
|
}
|
||||||
throw e
|
throw e
|
||||||
|
} finally {
|
||||||
|
signal?.removeEventListener("abort", onAbort)
|
||||||
}
|
}
|
||||||
|
if (signal?.aborted) throw new Error("Join cancelled")
|
||||||
await room.localParticipant.setMicrophoneEnabled(true)
|
await room.localParticipant.setMicrophoneEnabled(true)
|
||||||
|
|
||||||
currentVoiceSession.set({url, h, room, muted: false})
|
currentVoiceSession.set({url, h, room, muted: false})
|
||||||
|
|||||||
Reference in New Issue
Block a user