import { useEffect, useRef } from 'react'; import { Track } from 'livekit-client'; import type { Participant, TrackPublication, Room } from 'livekit-client'; import { useVoiceStore } from '../stores/voice.ts'; function VideoTile({ participant, isLocal, room }: { participant: Participant; isLocal: boolean; room: Room }) { const videoRef = useRef(null); useEffect(() => { const el = videoRef.current; if (!el) return; let pub: TrackPublication | undefined; const attachTrack = () => { pub = participant.getTrackPublication(Track.Source.Camera); if (pub?.track && el) { pub.track.attach(el); el.muted = isLocal; el.play().catch(() => {}); } }; const detachTrack = () => { if (pub?.track && el) { pub.track.detach(el); } }; attachTrack(); // Listen for track publications changing on this participant via room events const handlePublished = (pub: TrackPublication) => { pub.track?.attach(el); el.play().catch(() => {}); }; room.on('trackPublished' as any, handlePublished); return () => { detachTrack(); room.off('trackPublished' as any, handlePublished); }; }, [participant, isLocal, room]); const username = participant.name || participant.identity; return (
); } export function VideoGrid() { const room = useVoiceStore((s) => s._room); const isVideoOn = useVoiceStore((s) => s.isVideoOn); if (!room || !isVideoOn) return null; const participants: Participant[] = [ room.localParticipant, ...Array.from(room.remoteParticipants.values()), ]; const hasCam = participants.filter((p) => { const pub = p.getTrackPublication(Track.Source.Camera); return pub && !pub.isMuted; }); if (hasCam.length === 0) return null; return (
── video [{hasCam.length}] ──
{participants.map((p) => ( ))}
); }