Volver a la wiki

Panel de ajustes de dispositivos en MeetingRoom (cám/mic/altavoz)

Panel de ajustes de dispositivos en MeetingRoom (cám/mic/altavoz)

Contexto y motivación

Edu reportó que tras el primer deploy WebRTC, Windows elegía automáticamente la cámara del smartphone conectado vía Phone Link en lugar de la webcam física. Además, no existía ningún botón de configuración en la interfaz para corregirlo manualmente.

El commit 1b8a61e (s79 · 2026-05-22) introduce un panel completo de selección de dispositivos audiovisuales que resuelve este problema y añade persistencia entre sesiones.


Funcionalidades añadidas

Botón «Ajustes» en la barra de controles

Se añade un botón tools-btn junto a «Compartir pantalla» y «Colgar» que activa/desactiva el panel overlay de ajustes.

<button type="button" onClick={() => setSettingsOpen((s) => !s)} className="tools-btn">
  {settingsOpen ? 'Cerrar ajustes' : 'Ajustes'}
</button>

Panel overlay con tres dropdowns

El panel se renderiza como position: absolute anclado a la esquina inferior-derecha del modal (right: 8, bottom: 56). Contiene tres selectores:

SelectorlocalStorage keyConstraint aplicado
Cámara (videoinput)meeting:camIdvideo: { deviceId: { exact } }
Micrófono (audioinput)meeting:micIdaudio: { deviceId: { exact } }
Altavoz (audiooutput)meeting:speakerIdHTMLVideoElement.setSinkId()

Flujo técnico

1. Inicialización con persistencia

Al montar el componente, los tres estados se inicializan leyendo localStorage:

const [selectedCamId, setSelectedCamId] = useState<string>(
  () => localStorage.getItem('meeting:camId') || '',
);

Si hay un ID guardado, getUserMedia arranca directamente con ese deviceId: { exact }. Si el dispositivo ya no existe (p.ej. USB desconectado), hay un fallback automático a getUserMedia({ video: true, audio: true }) sin constraint:

try {
  stream = await navigator.mediaDevices.getUserMedia({ video: videoConstraint, audio: audioConstraint });
} catch (constraintErr) {
  console.warn('[meeting] device constraint failed, fallback default', constraintErr);
  stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
}

2. Enumeración de dispositivos

refreshDevices() llama a navigator.mediaDevices.enumerateDevices() para poblar las listas de cámaras, micrófonos y altavoces. Se invoca:

El listener se registra con addEventListener('devicechange', refreshDevices) y se limpia en el cleanup del useEffect.

3. Cambio de cámara en vivo (applyCameraChange)

getUserMedia({ video: { deviceId: { exact: id } }, audio: false })
  → nuevo VideoTrack
  → RTCRtpSender.replaceTrack() en cada peer (sin colgar)
  → removeTrack / addTrack en originalStreamRef
  → setLocalStream() para refrescar el preview local
  → localStorage.setItem('meeting:camId', id)

Si screenSharing está activo, el preview local no se actualiza (la pantalla sigue como fuente visual de la transmisión).

4. Cambio de micrófono en vivo (applyMicChange)

Mismo patrón que la cámara pero con audioinput:

getUserMedia({ audio: { deviceId: { exact: id } }, video: false })
  → nuevo AudioTrack
  → RTCRtpSender.replaceTrack() en cada peer
  → removeTrack / addTrack en originalStreamRef
  → localStorage.setItem('meeting:micId', id)

5. Cambio de altavoz (applySpeakerChange)

A diferencia de cámara/mic, el altavoz no requiere un nuevo stream: se aplica setSinkId(deviceId) sobre todos los elementos <video> del DOM. Esto incluye los tiles remotos.

const videos = document.querySelectorAll<HTMLVideoElement>('video');
for (const v of Array.from(videos)) {
  if (typeof v.setSinkId === 'function') {
    await v.setSinkId(deviceId);
  }
}

Compatibilidad: setSinkId solo está soportado en Chromium (Chrome, Edge). Firefox y Safari ignoran la selección y usan el dispositivo de salida del sistema. La UI muestra una nota informativa al respecto.


Estado del componente (nuevos useState)

EstadoTipoPropósito
settingsOpenbooleanVisibilidad del panel overlay
camerasMediaDeviceInfo[]Lista de cámaras disponibles
microphonesMediaDeviceInfo[]Lista de micrófonos disponibles
speakersMediaDeviceInfo[]Lista de altavoces disponibles
selectedCamIdstringID persistido / seleccionado de cámara
selectedMicIdstringID persistido / seleccionado de micrófono
selectedSpeakerIdstringID persistido / seleccionado de altavoz

Casos de borde cubiertos


Notas de implementación


Véase también

Subir