diff --git a/src/app/call/voice.ts b/src/app/call/voice.ts index cdf06029..145c8168 100644 --- a/src/app/call/voice.ts +++ b/src/app/call/voice.ts @@ -19,6 +19,7 @@ import {map, not, nthEq, reject, removeUndefined, uniqBy} from "@welshman/lib" import type {TrustedEvent} from "@welshman/util" import {makeHttpAuth, makeHttpAuthHeader, getTags} from "@welshman/util" import {signer} from "@welshman/app" +import {load} from "@welshman/net" import {getLivekitEndpoint} from "$lib/livekit" import {AbortError, TimeoutError, whenAborted, whenTimeout} from "$lib/util" import { @@ -154,6 +155,12 @@ const fetchLivekitToken = async ( return response.json() } +export const loadVoiceParticipants = (url: string, h: string) => + load({ + relays: [url], + filters: [{kinds: [LIVEKIT_PARTICIPANTS], "#d": [h]}], + }) + export const deriveVoiceParticipants = (url: string, h: string) => // We use the livekit identity list while in a call, and fall back to the list in kind 39004. derived( @@ -173,7 +180,7 @@ export const deriveVoiceParticipants = (url: string, h: string) => if (!latestEvent) return [] const participants = removeUndefined( map( - (tag: string[]) => (tag[1] ? {pubkey: tag[1], identity: tag[1]} : undefined), + (tag: string[]) => (tag[1] ? participantFromLiveKitIdentity(tag[1]) : undefined), getTags("participant", latestEvent.tags), ), ) diff --git a/src/app/components/ProfileCircles.svelte b/src/app/components/ProfileCircles.svelte index 687c30bb..8d161157 100644 --- a/src/app/components/ProfileCircles.svelte +++ b/src/app/components/ProfileCircles.svelte @@ -1,4 +1,5 @@ -
- {#each visiblePubkeys.toSorted().slice(0, limit) as pubkey (pubkey)} +
+ {#each displayPubkeys as pubkey (pubkey)}
- + class={cx( + "z-feature inline-block flex items-center justify-center rounded-full bg-base-100", + dimensions.box, + dimensions.overlap, + )}> +
{/each} + {#if overflowCount > 0} +
+ +{overflowCount} +
+ {/if}
diff --git a/src/app/components/VoiceRoomItem.svelte b/src/app/components/VoiceRoomItem.svelte index bf04855d..6145687a 100644 --- a/src/app/components/VoiceRoomItem.svelte +++ b/src/app/components/VoiceRoomItem.svelte @@ -4,6 +4,7 @@ import {loadProfile, displayProfileByPubkey} from "@welshman/app" import SecondaryNavItem from "@lib/components/SecondaryNavItem.svelte" import ProfileCircle from "@app/components/ProfileCircle.svelte" + import ProfileCircles from "@app/components/ProfileCircles.svelte" import RoomImage from "@app/components/RoomImage.svelte" import RoomName from "@app/components/RoomName.svelte" import {makeRoomPath} from "@app/util/routes" @@ -20,7 +21,11 @@ voiceState, type VoiceParticipant, } from "@app/call/stores" - import {cancelJoinVoiceRoom, deriveVoiceParticipants} from "@app/call/voice" + import { + cancelJoinVoiceRoom, + deriveVoiceParticipants, + loadVoiceParticipants, + } from "@app/call/voice" interface Props { url: string @@ -32,6 +37,7 @@ const {url, h, replaceState = false, notification = false}: Props = $props() const participants = deriveVoiceParticipants(url, h) + const participantPubkeys = $derived($participants.flatMap(p => (p.pubkey ? [p.pubkey] : []))) const isActive = $derived( $voiceState === VoiceState.Connected && $currentVoiceRoom?.id === makeRoomId(url, h), ) @@ -53,6 +59,10 @@ pushModal(VoiceRoomJoinDialog, {url, h}) } + $effect(() => { + void loadVoiceParticipants(url, h) + }) + $effect(() => { for (const p of $participants) { if (p.pubkey) loadProfile(p.pubkey) @@ -75,29 +85,33 @@ {/if}
- {#if $participants.length > 0} - {#each $participants as p (participantKey(p as VoiceParticipant))} -
-
- -
- - {p.pubkey ? displayProfileByPubkey(p.pubkey) : "Unknown"} - - {#if isActive} - {@const media = $mediaStateByIdentity(p.identity)} + {#if participantPubkeys.length > 0} + {#if isActive} + {#each $participants as p (participantKey(p as VoiceParticipant))} + {@const media = $mediaStateByIdentity(p.identity)} +
+
+ +
+ + {p.pubkey ? displayProfileByPubkey(p.pubkey) : "Unknown"} + - {/if} +
+ {/each} + {:else} +
+
- {/each} + {/if} {/if}
diff --git a/src/app/components/VoiceRoomJoinDialog.svelte b/src/app/components/VoiceRoomJoinDialog.svelte index 7da58f63..35ac5282 100644 --- a/src/app/components/VoiceRoomJoinDialog.svelte +++ b/src/app/components/VoiceRoomJoinDialog.svelte @@ -13,8 +13,9 @@ import ModalSubtitle from "@lib/components/ModalSubtitle.svelte" import ModalTitle from "@lib/components/ModalTitle.svelte" import {AbortError, TimeoutError} from "$lib/util" + import ProfileCircles from "@app/components/ProfileCircles.svelte" import {displayRoom} from "@app/core/state" - import {joinVoiceRoom} from "@app/call/voice" + import {deriveVoiceParticipants, joinVoiceRoom, loadVoiceParticipants} from "@app/call/voice" import {popModal} from "@app/util/modal" import {pushToast} from "@app/util/toast" @@ -26,6 +27,8 @@ const {url, h}: Props = $props() const spaceLabel = $derived(displayRelayUrl(url)) + const participants = deriveVoiceParticipants(url, h) + const participantPubkeys = $derived($participants.flatMap(p => (p.pubkey ? [p.pubkey] : []))) let audioInputs = $state([]) let selectedDeviceId = $state("") @@ -42,6 +45,7 @@ } $effect(() => { + void loadVoiceParticipants(url, h) void loadDevices() }) @@ -81,6 +85,11 @@ + {#if participantPubkeys.length > 0} +
+ +
+ {/if}

Select a microphone to join the call: