Cuando se desarrolla contenido para la Web, el estándar WebRTC proporciona API a fin de acceder a cámaras y micrófonos conectados a la computadora o smartphone. Por lo general, estos dispositivos se conocen como dispositivos multimedia y se puede acceder a ellos con JavaScript a través del objeto navigator.mediaDevices
, que implementa la interfaz MediaDevices
. Desde este objeto, podemos enumerar todos los dispositivos conectados, detectar los cambios en ellos (cuando se conecta o desconecta) y abrir uno para recuperar una transmisión multimedia (consulta a continuación).
La forma más común en la que se usa es a través de la función getUserMedia()
, que muestra una promesa que se resolverá como un MediaStream
para los dispositivos multimedia coincidentes. Esta función toma un solo objeto MediaStreamConstraints
que especifica los requisitos que tenemos. Por ejemplo, para abrir el micrófono y la cámara predeterminados, haremos lo siguiente.
Cómo usar promesas
const constraints = {
'video': true,
'audio': true
}
navigator.mediaDevices.getUserMedia(constraints)
.then(stream => {
console.log('Got MediaStream:', stream);
})
.catch(error => {
console.error('Error accessing media devices.', error);
});
Cómo usar async/await
const openMediaDevices = async (constraints) => {
return await navigator.mediaDevices.getUserMedia(constraints);
}
try {
const stream = openMediaDevices({'video':true,'audio':true});
console.log('Got MediaStream:', stream);
} catch(error) {
console.error('Error accessing media devices.', error);
}
La llamada a getUserMedia()
activará una solicitud de permisos. Si el usuario acepta el permiso, la promesa se resuelve con un objeto MediaStream
que contiene un video y una pista de audio. Si se niega el permiso, se genera una PermissionDeniedError
. En caso de que no haya dispositivos coincidentes conectados, se arrojará una NotFoundError
.
La referencia completa de la API para la interfaz MediaDevices
está disponible en documentos web de MDN.
Consulta de dispositivos multimedia
En una aplicación más compleja, lo más probable es que sea conveniente revisar todas las cámaras y los micrófonos conectados y proporcionar los comentarios adecuados al usuario. Para ello, llama a la función enumerateDevices()
. Se mostrará una promesa que se resuelve en un array de MediaDevicesInfo
que describe cada dispositivo multimedia conocido. Podemos usarlo a fin de presentarle una IU al usuario para que elija la que prefiera. Cada MediaDevicesInfo
contiene una propiedad llamada kind
con el valor audioinput
, audiooutput
o videoinput
, que indica el tipo de dispositivo multimedia que es.
Cómo usar promesas
function getConnectedDevices(type, callback) {
navigator.mediaDevices.enumerateDevices()
.then(devices => {
const filtered = devices.filter(device => device.kind === type);
callback(filtered);
});
}
getConnectedDevices('videoinput', cameras => console.log('Cameras found', cameras));
Cómo usar async/await
async function getConnectedDevices(type) {
const devices = await navigator.mediaDevices.enumerateDevices();
return devices.filter(device => device.kind === type)
}
const videoCameras = getConnectedDevices('videoinput');
console.log('Cameras found:', videoCameras);
Detectando cambios en los dispositivos
La mayoría de las computadoras son compatibles con la conexión de varios dispositivos durante el tiempo de ejecución. Podría ser una cámara web conectada mediante USB, auriculares Bluetooth o un conjunto de bocinas externas. Con el fin de admitir esto correctamente, una aplicación web debe detectar los cambios de los dispositivos multimedia. Para ello, agrega un objeto de escucha a navigator.mediaDevices
para el evento devicechange
.
// Updates the select element with the provided set of cameras
function updateCameraList(cameras) {
const listElement = document.querySelector('select#availableCameras');
listElement.innerHTML = '';
cameras.map(camera => {
const cameraOption = document.createElement('option');
cameraOption.label = camera.label;
cameraOption.value = camera.deviceId;
}).forEach(cameraOption => listElement.add(cameraOption));
}
// Fetch an array of devices of a certain type
async function getConnectedDevices(type) {
const devices = await navigator.mediaDevices.enumerateDevices();
return devices.filter(device => device.kind === type)
}
// Get the initial set of cameras connected
const videoCameras = getConnectedDevices('videoinput');
updateCameraList(videoCameras);
// Listen for changes to media devices and update the list accordingly
navigator.mediaDevices.addEventListener('devicechange', event => {
const newCameraList = getConnectedDevices('video');
updateCameraList(newCameraList);
});
Restricciones de contenido multimedia
El objeto constraints, que debe implementar la interfaz MediaStreamConstraints
, que pasamos como parámetro a getUserMedia()
, nos permite abrir un dispositivo multimedia que cumple con un requisito determinado. Este requisito se puede definir de manera muy flexible (audio o video) o muy específico (resolución mínima de la cámara o un ID de dispositivo exacto). Se recomienda que las aplicaciones que usan la API de getUserMedia()
verifiquen primero los dispositivos existentes y, luego, especifiquen una restricción que coincida con el dispositivo exacto mediante la restricción deviceId
.
Si es posible, los dispositivos se configurarán según las restricciones. Podemos habilitar la cancelación del eco en micrófonos o establecer un ancho y un alto específicos o mínimos del video desde la cámara.
async function getConnectedDevices(type) {
const devices = await navigator.mediaDevices.enumerateDevices();
return devices.filter(device => device.kind === type)
}
// Open camera with at least minWidth and minHeight capabilities
async function openCamera(cameraId, minWidth, minHeight) {
const constraints = {
'audio': {'echoCancellation': true},
'video': {
'deviceId': cameraId,
'width': {'min': minWidth},
'height': {'min': minHeight}
}
}
return await navigator.mediaDevices.getUserMedia(constraints);
}
const cameras = getConnectedDevices('videoinput');
if (cameras && cameras.length > 0) {
// Open first available video camera with a resolution of 1280x720 pixels
const stream = openCamera(cameras[0].deviceId, 1280, 720);
}
Puedes encontrar la documentación completa de la interfaz de MediaStreamConstraints
en los documentos web de MDN.
Reproducción local
Una vez que se abre un dispositivo multimedia y tenemos un MediaStream
disponible, podemos asignarlo a un elemento de audio o video para reproducir la transmisión de manera local.
async function playVideoFromCamera() {
try {
const constraints = {'video': true, 'audio': true};
const stream = await navigator.mediaDevices.getUserMedia(constraints);
const videoElement = document.querySelector('video#localVideo');
videoElement.srcObject = stream;
} catch(error) {
console.error('Error opening video camera.', error);
}
}
El código HTML necesario para un elemento de video típico que se usa con getUserMedia()
suele tener los atributos autoplay
y playsinline
. El atributo autoplay
hará que las nuevas transmisiones asignadas al elemento se reproduzcan automáticamente.
El atributo playsinline
permite que el video se reproduzca intercalado, en lugar de solo en pantalla completa, en ciertos navegadores para dispositivos móviles. También se recomienda usar controls="false"
para las transmisiones en vivo, a menos que el usuario pueda pausarlas.
<html>
<head><title>Local video playback</title></head>
<body>
<video id="localVideo" autoplay playsinline controls="false"/>
</body>
</html>