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() { export function VideoGrid() {
const room = useVoiceStore((s) => s._room); 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[] = [ const hasCamParticipants = voiceParticipants.filter((p) => p.hasVideo);
room.localParticipant,
...Array.from(room.remoteParticipants.values()),
];
const hasCam = participants.filter((p) => { if (hasCamParticipants.length === 0) return null;
const pub = p.getTrackPublication(Track.Source.Camera);
return pub && !pub.isMuted;
});
if (hasCam.length === 0) return null; const getLiveKitParticipant = (identity: string) => {
if (identity === room.localParticipant.identity) return room.localParticipant;
return room.remoteParticipants.get(identity);
};
return ( return (
<div className="px-3 py-1.5 border-b border-gb-bg-t"> <div className="px-3 py-1.5 border-b border-gb-bg-t">
<div className="text-xxs text-gb-fg-f mb-1"> <div className="text-xxs text-gb-fg-f mb-1">
video [{hasCam.length}] video [{hasCamParticipants.length}]
</div> </div>
<div <div
className={`grid gap-1 ${ className={`grid gap-1 ${
hasCam.length <= 2 hasCamParticipants.length === 1
? 'grid-cols-2' ? 'grid-cols-1'
: hasCam.length <= 4 : hasCamParticipants.length <= 4
? 'grid-cols-2' ? 'grid-cols-2'
: 'grid-cols-3' : 'grid-cols-3'
}`} }`}
> >
{participants.map((p) => ( {hasCamParticipants.map((vp) => {
const p = getLiveKitParticipant(vp.identity);
if (!p) return null;
return (
<VideoTile <VideoTile
key={p.identity} key={p.identity}
participant={p} participant={p}
isLocal={p.identity === room.localParticipant.identity} isLocal={p.identity === room.localParticipant.identity}
room={room} room={room}
/> />
))} );
})}
</div> </div>
</div> </div>
); );
+10 -2
View File
@@ -18,6 +18,7 @@ export interface VoiceParticipant {
username: string; username: string;
isMuted: boolean; isMuted: boolean;
isSpeaking: boolean; isSpeaking: boolean;
hasVideo: boolean;
} }
export interface WhisperNotification { export interface WhisperNotification {
@@ -50,8 +51,8 @@ interface VoiceState {
leaveVoice: () => Promise<void>; leaveVoice: () => Promise<void>;
toggleMute: () => void; toggleMute: () => void;
toggleDeafen: () => void; toggleDeafen: () => void;
toggleVideo: () => void; toggleVideo: () => Promise<void>;
toggleScreenShare: () => void; toggleScreenShare: () => Promise<void>;
toggleNoiseSuppression: () => void; toggleNoiseSuppression: () => void;
_addWhisper: (w: WhisperNotification) => void; _addWhisper: (w: WhisperNotification) => void;
whisperTo: (targetUserId: string, targetUsername: string, message?: string) => void; whisperTo: (targetUserId: string, targetUsername: string, message?: string) => void;
@@ -60,11 +61,13 @@ interface VoiceState {
function participantToVoice(p: Participant): VoiceParticipant { function participantToVoice(p: Participant): VoiceParticipant {
const micPub = p.getTrackPublication(Track.Source.Microphone); const micPub = p.getTrackPublication(Track.Source.Microphone);
const camPub = p.getTrackPublication(Track.Source.Camera);
return { return {
identity: p.identity, identity: p.identity,
username: p.name || p.identity, username: p.name || p.identity,
isMuted: micPub?.isMuted ?? true, isMuted: micPub?.isMuted ?? true,
isSpeaking: p.isSpeaking, isSpeaking: p.isSpeaking,
hasVideo: !!(camPub && !camPub.isMuted),
}; };
} }
@@ -154,6 +157,11 @@ export const useVoiceStore = create<VoiceState>((set, get) => ({
syncParticipants(); syncParticipants();
}); });
room.on(RoomEvent.TrackPublished, () => syncParticipants());
room.on(RoomEvent.TrackUnpublished, () => syncParticipants());
room.on(RoomEvent.TrackSubscribed, () => syncParticipants());
room.on(RoomEvent.TrackUnsubscribed, () => syncParticipants());
room.on( room.on(
RoomEvent.ActiveSpeakersChanged, RoomEvent.ActiveSpeakersChanged,
(speakers: Participant[]) => { (speakers: Participant[]) => {