fix(webrtc): fix VideoGrid reactivity and layout

This commit is contained in:
2026-07-06 12:37:35 +00:00
parent 074742ddb4
commit 5d8f420882
2 changed files with 34 additions and 25 deletions
+18 -17
View File
@@ -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 (
<div className="px-3 py-1.5 border-b border-gb-bg-t">
<div className="text-xxs text-gb-fg-f mb-1">
video [{hasCam.length}]
video [{hasCamParticipants.length}]
</div>
<div
className={`grid gap-1 ${
hasCam.length <= 2
? 'grid-cols-2'
: hasCam.length <= 4
hasCamParticipants.length === 1
? 'grid-cols-1'
: hasCamParticipants.length <= 4
? 'grid-cols-2'
: 'grid-cols-3'
}`}
>
{participants.map((p) => (
{hasCamParticipants.map((vp) => {
const p = getLiveKitParticipant(vp.identity);
if (!p) return null;
return (
<VideoTile
key={p.identity}
participant={p}
isLocal={p.identity === room.localParticipant.identity}
room={room}
/>
))}
);
})}
</div>
</div>
);
+10 -2
View File
@@ -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<void>;
toggleMute: () => void;
toggleDeafen: () => void;
toggleVideo: () => void;
toggleScreenShare: () => void;
toggleVideo: () => Promise<void>;
toggleScreenShare: () => Promise<void>;
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<VoiceState>((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[]) => {