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() {
|
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
@@ -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[]) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user