diff --git a/packages/call/src/components/modals/base.tsx b/packages/call/src/components/modals/base.tsx
index 6b9593b..cf91d34 100644
--- a/packages/call/src/components/modals/base.tsx
+++ b/packages/call/src/components/modals/base.tsx
@@ -6,7 +6,7 @@ import {
} from "@tensamin/ui";
import {
focusParticipant,
- getParsedParticipantMetadata,
+ getRoomMetadata,
setCallView,
useCall,
} from "../../store";
@@ -14,7 +14,7 @@ import { Track, type Participant } from "livekit-client";
import { useEffect, useState } from "react";
import { useUser, type User } from "@tensamin/user/context";
import VideoViewer from "../videoViewer";
-import { Loader2, MicOff, Monitor, Shield } from "lucide-react";
+import { HeadphoneOff, Loader2, MicOff, Monitor, Shield } from "lucide-react";
function TransparentButton({ children }: { children: React.ReactNode }) {
return (
@@ -33,9 +33,8 @@ function Overlay({
user: User;
participant: Participant;
}) {
- const { isAdmin } = getParsedParticipantMetadata(user.user_id) || {
- isAdmin: false,
- };
+ const isAdmin = getRoomMetadata()?.admin === user.user_id;
+ const isDeafened = participant.attributes["deafened"] === "true";
return (
@@ -46,6 +45,11 @@ function Overlay({
)}
+ {isDeafened && (
+
+
+
+ )}
{isAdmin && (
diff --git a/packages/call/src/store.tsx b/packages/call/src/store.tsx
index 0a5db55..d0173fc 100644
--- a/packages/call/src/store.tsx
+++ b/packages/call/src/store.tsx
@@ -70,10 +70,6 @@ type Runtime = {
getUser: GetUserFn;
};
-type ParticipantMetadata = {
- isAdmin: boolean;
-};
-
type CallStore = {
state: CallState;
view: CallView;
@@ -90,7 +86,6 @@ type CallStore = {
watchedStreamParticipantIds: number[];
pendingWatchedParticipantIds: number[];
activeScreenShareParticipantIds: number[];
- parsedParticipantMetadataById: Record;
isEncrypted: boolean;
room: Room;
keyProvider: ExternalE2EEKeyProvider;
@@ -186,51 +181,6 @@ function hasParticipant(participantId: number) {
);
}
-function parseParticipantMetadata(
- metadata: string | undefined,
-): ParticipantMetadata | undefined {
- if (!metadata) {
- return undefined;
- }
-
- try {
- return JSON.parse(metadata);
- } catch {
- return undefined;
- }
-}
-
-function getParsedParticipantMetadataById(): Record<
- number,
- ParticipantMetadata
-> {
- return Object.fromEntries(
- getAllParticipants()
- .map((participant) => {
- const participantId = getParticipantId(participant.identity);
-
- if (participantId == null) {
- return null;
- }
-
- return [
- participantId,
- parseParticipantMetadata(participant.metadata),
- ] as const;
- })
- .filter(
- (entry): entry is readonly [number, ParticipantMetadata] =>
- entry != null,
- ),
- );
-}
-
-function syncParsedParticipantMetadata() {
- useCall.setState({
- parsedParticipantMetadataById: getParsedParticipantMetadataById(),
- });
-}
-
function syncScreenShareParticipants() {
const activeScreenShareParticipantIds = getActiveScreenShareParticipantIds();
const state = useCall.getState();
@@ -268,6 +218,13 @@ function requireRuntime(runtime: Runtime | null): Runtime {
return runtime;
}
+export function getRoomMetadata() {
+ const roomMetadata = room.metadata;
+ return JSON.parse(roomMetadata || '{"admin": 0}').catch(() => ({
+ admin: 0,
+ })) as { admin: number };
+}
+
// Sync local participant flags and screen-share derived state for the active call UI.
export function syncParticipantState() {
const { room, screenShareSession } = useCall.getState();
@@ -281,13 +238,6 @@ export function syncParticipantState() {
});
syncScreenShareParticipants();
- syncParsedParticipantMetadata();
-}
-
-export function getParsedParticipantMetadata(
- participantId: number,
-): ParticipantMetadata | undefined {
- return useCall.getState().parsedParticipantMetadataById[participantId];
}
// set state functions
@@ -525,7 +475,6 @@ export async function disconnect() {
watchedStreamParticipantIds: [],
pendingWatchedParticipantIds: [],
activeScreenShareParticipantIds: [],
- parsedParticipantMetadataById: {},
});
room.remoteParticipants.forEach((participant) => {
@@ -604,7 +553,7 @@ export async function joinCall(
}
// Mute or restore incoming call audio for every remote participant.
-export function toggleDeaf() {
+export async function toggleDeaf() {
const nextDeaf = !useCall.getState().deaf;
room.remoteParticipants.forEach((participant) => {
@@ -612,9 +561,13 @@ export function toggleDeaf() {
});
if (nextDeaf && room.localParticipant.isMicrophoneEnabled) {
- toggleMute();
+ await toggleMute();
}
+ await room.localParticipant.setAttributes({
+ deafened: nextDeaf ? "true" : "false",
+ });
+
useCall.setState({ deaf: nextDeaf });
}
@@ -623,7 +576,7 @@ export async function toggleMute() {
const micEnabled = useCall.getState().micEnabled;
if (!micEnabled && useCall.getState().deaf) {
- toggleDeaf();
+ await toggleDeaf();
}
await room.localParticipant.setMicrophoneEnabled(!micEnabled);
@@ -669,7 +622,6 @@ export function resetCallState() {
watchedStreamParticipantIds: [],
pendingWatchedParticipantIds: [],
activeScreenShareParticipantIds: [],
- parsedParticipantMetadataById: {},
});
syncParticipantState();
@@ -711,7 +663,6 @@ export const useCall = create(() => ({
watchedStreamParticipantIds: [],
pendingWatchedParticipantIds: [],
activeScreenShareParticipantIds: [],
- parsedParticipantMetadataById: {},
isEncrypted:
room.localParticipant.isE2EEEnabled && room.localParticipant.isEncrypted,
room,