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: