(feat): save deafen state in participant metadata
(feat): move admin check to room metadata
This commit is contained in:
parent
c7313da53f
commit
c39185c200
2 changed files with 23 additions and 68 deletions
|
|
@ -6,7 +6,7 @@ import {
|
||||||
} from "@tensamin/ui";
|
} from "@tensamin/ui";
|
||||||
import {
|
import {
|
||||||
focusParticipant,
|
focusParticipant,
|
||||||
getParsedParticipantMetadata,
|
getRoomMetadata,
|
||||||
setCallView,
|
setCallView,
|
||||||
useCall,
|
useCall,
|
||||||
} from "../../store";
|
} from "../../store";
|
||||||
|
|
@ -14,7 +14,7 @@ import { Track, type Participant } from "livekit-client";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useUser, type User } from "@tensamin/user/context";
|
import { useUser, type User } from "@tensamin/user/context";
|
||||||
import VideoViewer from "../videoViewer";
|
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 }) {
|
function TransparentButton({ children }: { children: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
|
|
@ -33,9 +33,8 @@ function Overlay({
|
||||||
user: User;
|
user: User;
|
||||||
participant: Participant;
|
participant: Participant;
|
||||||
}) {
|
}) {
|
||||||
const { isAdmin } = getParsedParticipantMetadata(user.user_id) || {
|
const isAdmin = getRoomMetadata()?.admin === user.user_id;
|
||||||
isAdmin: false,
|
const isDeafened = participant.attributes["deafened"] === "true";
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-auto flex items-center gap-1">
|
<div className="w-full h-auto flex items-center gap-1">
|
||||||
|
|
@ -46,6 +45,11 @@ function Overlay({
|
||||||
<MicOff className="size-3.5" />
|
<MicOff className="size-3.5" />
|
||||||
</TransparentButton>
|
</TransparentButton>
|
||||||
)}
|
)}
|
||||||
|
{isDeafened && (
|
||||||
|
<TransparentButton>
|
||||||
|
<HeadphoneOff className="size-3.5" />
|
||||||
|
</TransparentButton>
|
||||||
|
)}
|
||||||
{isAdmin && (
|
{isAdmin && (
|
||||||
<TransparentButton>
|
<TransparentButton>
|
||||||
<Shield className="size-3.5" />
|
<Shield className="size-3.5" />
|
||||||
|
|
|
||||||
|
|
@ -70,10 +70,6 @@ type Runtime = {
|
||||||
getUser: GetUserFn;
|
getUser: GetUserFn;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ParticipantMetadata = {
|
|
||||||
isAdmin: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
type CallStore = {
|
type CallStore = {
|
||||||
state: CallState;
|
state: CallState;
|
||||||
view: CallView;
|
view: CallView;
|
||||||
|
|
@ -90,7 +86,6 @@ type CallStore = {
|
||||||
watchedStreamParticipantIds: number[];
|
watchedStreamParticipantIds: number[];
|
||||||
pendingWatchedParticipantIds: number[];
|
pendingWatchedParticipantIds: number[];
|
||||||
activeScreenShareParticipantIds: number[];
|
activeScreenShareParticipantIds: number[];
|
||||||
parsedParticipantMetadataById: Record<number, ParticipantMetadata>;
|
|
||||||
isEncrypted: boolean;
|
isEncrypted: boolean;
|
||||||
room: Room;
|
room: Room;
|
||||||
keyProvider: ExternalE2EEKeyProvider;
|
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() {
|
function syncScreenShareParticipants() {
|
||||||
const activeScreenShareParticipantIds = getActiveScreenShareParticipantIds();
|
const activeScreenShareParticipantIds = getActiveScreenShareParticipantIds();
|
||||||
const state = useCall.getState();
|
const state = useCall.getState();
|
||||||
|
|
@ -268,6 +218,13 @@ function requireRuntime(runtime: Runtime | null): Runtime {
|
||||||
return 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.
|
// Sync local participant flags and screen-share derived state for the active call UI.
|
||||||
export function syncParticipantState() {
|
export function syncParticipantState() {
|
||||||
const { room, screenShareSession } = useCall.getState();
|
const { room, screenShareSession } = useCall.getState();
|
||||||
|
|
@ -281,13 +238,6 @@ export function syncParticipantState() {
|
||||||
});
|
});
|
||||||
|
|
||||||
syncScreenShareParticipants();
|
syncScreenShareParticipants();
|
||||||
syncParsedParticipantMetadata();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getParsedParticipantMetadata(
|
|
||||||
participantId: number,
|
|
||||||
): ParticipantMetadata | undefined {
|
|
||||||
return useCall.getState().parsedParticipantMetadataById[participantId];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// set state functions
|
// set state functions
|
||||||
|
|
@ -525,7 +475,6 @@ export async function disconnect() {
|
||||||
watchedStreamParticipantIds: [],
|
watchedStreamParticipantIds: [],
|
||||||
pendingWatchedParticipantIds: [],
|
pendingWatchedParticipantIds: [],
|
||||||
activeScreenShareParticipantIds: [],
|
activeScreenShareParticipantIds: [],
|
||||||
parsedParticipantMetadataById: {},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
room.remoteParticipants.forEach((participant) => {
|
room.remoteParticipants.forEach((participant) => {
|
||||||
|
|
@ -604,7 +553,7 @@ export async function joinCall(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mute or restore incoming call audio for every remote participant.
|
// Mute or restore incoming call audio for every remote participant.
|
||||||
export function toggleDeaf() {
|
export async function toggleDeaf() {
|
||||||
const nextDeaf = !useCall.getState().deaf;
|
const nextDeaf = !useCall.getState().deaf;
|
||||||
|
|
||||||
room.remoteParticipants.forEach((participant) => {
|
room.remoteParticipants.forEach((participant) => {
|
||||||
|
|
@ -612,9 +561,13 @@ export function toggleDeaf() {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (nextDeaf && room.localParticipant.isMicrophoneEnabled) {
|
if (nextDeaf && room.localParticipant.isMicrophoneEnabled) {
|
||||||
toggleMute();
|
await toggleMute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await room.localParticipant.setAttributes({
|
||||||
|
deafened: nextDeaf ? "true" : "false",
|
||||||
|
});
|
||||||
|
|
||||||
useCall.setState({ deaf: nextDeaf });
|
useCall.setState({ deaf: nextDeaf });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -623,7 +576,7 @@ export async function toggleMute() {
|
||||||
const micEnabled = useCall.getState().micEnabled;
|
const micEnabled = useCall.getState().micEnabled;
|
||||||
|
|
||||||
if (!micEnabled && useCall.getState().deaf) {
|
if (!micEnabled && useCall.getState().deaf) {
|
||||||
toggleDeaf();
|
await toggleDeaf();
|
||||||
}
|
}
|
||||||
|
|
||||||
await room.localParticipant.setMicrophoneEnabled(!micEnabled);
|
await room.localParticipant.setMicrophoneEnabled(!micEnabled);
|
||||||
|
|
@ -669,7 +622,6 @@ export function resetCallState() {
|
||||||
watchedStreamParticipantIds: [],
|
watchedStreamParticipantIds: [],
|
||||||
pendingWatchedParticipantIds: [],
|
pendingWatchedParticipantIds: [],
|
||||||
activeScreenShareParticipantIds: [],
|
activeScreenShareParticipantIds: [],
|
||||||
parsedParticipantMetadataById: {},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
syncParticipantState();
|
syncParticipantState();
|
||||||
|
|
@ -711,7 +663,6 @@ export const useCall = create<CallStore>(() => ({
|
||||||
watchedStreamParticipantIds: [],
|
watchedStreamParticipantIds: [],
|
||||||
pendingWatchedParticipantIds: [],
|
pendingWatchedParticipantIds: [],
|
||||||
activeScreenShareParticipantIds: [],
|
activeScreenShareParticipantIds: [],
|
||||||
parsedParticipantMetadataById: {},
|
|
||||||
isEncrypted:
|
isEncrypted:
|
||||||
room.localParticipant.isE2EEEnabled && room.localParticipant.isEncrypted,
|
room.localParticipant.isE2EEEnabled && room.localParticipant.isEncrypted,
|
||||||
room,
|
room,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue