MeetingRoom — Componente de videollamada WebRTC
Archivo: src/components/widgets/MeetingRoom.tsx
Tipo: React component (island Astro, client:only="react")
Sesión de origen: s79 (22-05-2026) · Último refactor: s80 (22-05-2026, PR#68)
Descripción
MeetingRoom es el componente React principal que implementa las videollamadas peer-to-peer en el Workspace. Gestiona toda la lógica de señalización, negociación WebRTC y renderizado de la sala de reunión.
Modo de uso (refactor s80 · 22-05-2026): el componente vive ahora en una ventana popup propia del navegador (/reunion, abierta con window.open(..., 'popup=yes,...') desde TeamChat). Ocupa el 100% del viewport del popup. Sin drag/resize/portal — la ventana del SO ya cumple esos roles y de regalo permite arrastrar la reunión al segundo monitor. Refactor motivado por que el modal flotante embebido en el WS se desmontaba al navegar entre páginas Astro (output: ‘static’ sin View Transitions), matando la sesión WebRTC. Ver [[decision—20260522—meetingroom-popup-window]].
Responsabilidades
| Área | Detalle |
|---|---|
| Señalización | Envía/recibe señales (peer_joined, peer_left, ICE candidates, SDP offers/answers) mediante sendSignal y polling cada ~1.5 s |
| Gestión de peers | peersMapRef — mapa de RemotePeer con su RTCPeerConnection y estado de negociación |
| Streams locales | localStream (cámara/mic) y stream de pantalla compartida |
| Interfaz | Fullscreen dentro de la ventana popup; controles de audio, vídeo, pantalla y ajustes de dispositivos |
| Ajustes de dispositivos | Panel overlay para seleccionar cámara, micrófono y altavoz (desde PR#62, 2026-05-22) |
| Cleanup robusto | beforeunload + pagehide envían peer_left via navigator.sendBeacon cuando el usuario cierra el popup desde la X del SO (s80). Sin esto, los peers tardan ~5 s en detectar la desconexión. |
Props
interface Props {
/** Nombre del usuario que abre la reunión (Edu/Dani/Txell). */
author: string;
/** Destinatario ('Todos' o nombre concreto). */
target: string;
/**
* Si true, no anuncia «X ha iniciado una reunión» al chat.
* Usar cuando el usuario se une a una reunión ya convocada.
*/
silent?: boolean;
/** Cierre solicitado por el usuario (botón Colgar / Esc). El shell hace window.close(). */
onClose: () => void;
}
Tipos / Schemas internos
interface RemotePeer { peerId: string; name: string; stream: MediaStream | null; pc: RTCPeerConnection }
interface SignalMessage { room: string; from_peer: string; from_name: string; to_peer: string | null; kind: string; payload: unknown }
interface VideoTileProps { stream: MediaStream; label: string }
ICE servers (hardcoded): stun.l.google.com:19302, stun1.l.google.com:19302
Lifecycle destacado
useEffectde inicialización: llama agetUserMediarespetando selección previa →refreshDevices()→ añade listenerdevicechange.- Cleanup React:
removeEventListener('devicechange', ...), para tracks, cierraRTCPeerConnections, envíapeer_left. beforeunload+pagehide:navigator.sendBeacon('/api/webrtc/signal', {kind:'peer_left'})— garantiza que los otros peers detecten la salida sin esperar al timeout de connection-state (añadido en s80).- Polling de señales cada 1.5 s para recibir eventos de otros peers.
Persistencia localStorage
| Clave | Valor |
|---|---|
meeting:cameraId | deviceId de la cámara seleccionada |
meeting:micId | deviceId del micrófono seleccionado |
meeting:speakerId | deviceId del altavoz seleccionado |
Historial de cambios relevantes
| Sesión | Cambio |
|---|---|
| s79 (22-05-2026, PR#62) | Creación. Reemplaza JitsiMeetingModal. Modal flotante draggable + redimensionable con createPortal. |
| s80 (22-05-2026, PR#68) | Refactor a fullscreen popup: eliminados drag/resize/clampPos/collapsed/createPortal (~150 LOC). Añadido beforeunload/pagehide con sendBeacon. Vive en /reunion abierto por window.open desde TeamChat. |
Véase también
- [[feature—workspace—webrtc-peer-to-peer]] — Feature completa: WebRTC P2P en el workspace, arquitectura general
- [[decision—20260522—meetingroom-popup-window]] — ADR del cambio modal → popup (s80, PR#68)
- [[entity—meeting—component—meeting-room-shell]] — Wrapper standalone que monta este componente en /reunion
- [[decision—20260522—webrtc-reemplaza-jitsi]] — ADR previo: por qué se reemplazó Jitsi
- [[feature—meeting—ajustes-dispositivos]] — Feature de selección de cámara/micrófono/altavoz