fix(webrtc): fix VideoGrid reactivity and layout
This commit is contained in:
@@ -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) => (
|
||||
<VideoTile
|
||||
key={p.identity}
|
||||
participant={p}
|
||||
isLocal={p.identity === room.localParticipant.identity}
|
||||
room={room}
|
||||
/>
|
||||
))}
|
||||
{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
@@ -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[]) => {
|
||||
|
||||
Reference in New Issue
Block a user