diff --git a/web/src/components/VideoGrid.tsx b/web/src/components/VideoGrid.tsx index 59501ba..ac038ee 100644 --- a/web/src/components/VideoGrid.tsx +++ b/web/src/components/VideoGrid.tsx @@ -62,44 +62,45 @@ function VideoTile({ participant, isLocal, room }: { participant: Participant; i export function VideoGrid() { const room = useVoiceStore((s) => s._room); - const isVideoOn = useVoiceStore((s) => s.isVideoOn); + const voiceParticipants = useVoiceStore((s) => s.participants); - if (!room || !isVideoOn) return null; + if (!room) return null; - const participants: Participant[] = [ - room.localParticipant, - ...Array.from(room.remoteParticipants.values()), - ]; + const hasCamParticipants = voiceParticipants.filter((p) => p.hasVideo); - const hasCam = participants.filter((p) => { - const pub = p.getTrackPublication(Track.Source.Camera); - return pub && !pub.isMuted; - }); + if (hasCamParticipants.length === 0) return null; - if (hasCam.length === 0) return null; + const getLiveKitParticipant = (identity: string) => { + if (identity === room.localParticipant.identity) return room.localParticipant; + return room.remoteParticipants.get(identity); + }; return (
- ── video [{hasCam.length}] ── + ── video [{hasCamParticipants.length}] ──
- {participants.map((p) => ( - - ))} + {hasCamParticipants.map((vp) => { + const p = getLiveKitParticipant(vp.identity); + if (!p) return null; + return ( + + ); + })}
); diff --git a/web/src/stores/voice.ts b/web/src/stores/voice.ts index 3453022..114b90a 100644 --- a/web/src/stores/voice.ts +++ b/web/src/stores/voice.ts @@ -18,6 +18,7 @@ export interface VoiceParticipant { username: string; isMuted: boolean; isSpeaking: boolean; + hasVideo: boolean; } export interface WhisperNotification { @@ -50,8 +51,8 @@ interface VoiceState { leaveVoice: () => Promise; toggleMute: () => void; toggleDeafen: () => void; - toggleVideo: () => void; - toggleScreenShare: () => void; + toggleVideo: () => Promise; + toggleScreenShare: () => Promise; toggleNoiseSuppression: () => void; _addWhisper: (w: WhisperNotification) => void; whisperTo: (targetUserId: string, targetUsername: string, message?: string) => void; @@ -60,11 +61,13 @@ interface VoiceState { function participantToVoice(p: Participant): VoiceParticipant { const micPub = p.getTrackPublication(Track.Source.Microphone); + const camPub = p.getTrackPublication(Track.Source.Camera); return { identity: p.identity, username: p.name || p.identity, isMuted: micPub?.isMuted ?? true, isSpeaking: p.isSpeaking, + hasVideo: !!(camPub && !camPub.isMuted), }; } @@ -154,6 +157,11 @@ export const useVoiceStore = create((set, get) => ({ syncParticipants(); }); + room.on(RoomEvent.TrackPublished, () => syncParticipants()); + room.on(RoomEvent.TrackUnpublished, () => syncParticipants()); + room.on(RoomEvent.TrackSubscribed, () => syncParticipants()); + room.on(RoomEvent.TrackUnsubscribed, () => syncParticipants()); + room.on( RoomEvent.ActiveSpeakersChanged, (speakers: Participant[]) => {