Cómo comenzar a usar dispositivos de medios

Cuando se desarrolla para la Web, el estándar WebRTC proporciona APIs que permiten acceder a las cámaras y los micrófonos conectados a la computadora o el smartphone. Por lo general, estos dispositivos se denominan dispositivos de medios, 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 de los dispositivos (cuando se conecta o desconectar un dispositivo) y abrir un dispositivo para recuperar un flujo de contenido multimedia (consulta a continuación).

La forma más común en que se usa es a través de la función getUserMedia(), que muestra una promesa que se resolverá en una 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.

Usa 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);
    });

Usa 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 MediaStream que contiene una pista de video y una de audio. Si se niega el permiso, se genera una PermissionDeniedError. En caso de que no haya conectados dispositivos que coincidan, se arrojará una NotFoundError.

La referencia completa de la API para la interfaz MediaDevices está disponible en los documentos web de MDN.

Consulta de dispositivos de medios

En una aplicación más compleja, es recomendable que verifiques todas las cámaras y los micrófonos conectados, y le brindemos los comentarios adecuados al usuario. Para ello, se llama a la función enumerateDevices(). Se mostrará una promesa que se resuelve en un array de MediaDevicesInfo que describe cada dispositivo de medios conocido. Podemos usar esto para presentar una IU al usuario que le permita elegir la que prefiera. Cada MediaDevicesInfo contiene una propiedad llamada kind con el valor audioinput, audiooutput o videoinput, que indica qué tipo de dispositivo de medios es.

Usa 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));

Usa 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);

Cómo escuchar cambios en los dispositivos

La mayoría de las computadoras admiten la conexión de varios dispositivos durante el tiempo de ejecución. Puede ser una cámara web conectada mediante USB, auriculares Bluetooth o un conjunto de bocinas externas. Para admitir esto correctamente, una aplicación web debe detectar los cambios de los dispositivos de medios. Para ello, se debe agregar 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 constraint, 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 puede ser muy amplio (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() primero verifiquen los dispositivos existentes y, luego, especifique una restricción que coincida con el dispositivo exacto mediante la restricción deviceId. Si es posible, los dispositivos también se configurarán de acuerdo con las restricciones. Podemos habilitar la cancelación del eco en los micrófonos o establecer un ancho y una altura 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);
}

La documentación completa de la interfaz MediaStreamConstraints se encuentra en los documentos web de MDN.

Reproducción local

Una vez que se abre un dispositivo multimedia y hay un MediaStream disponible, podemos asignarlo a un elemento de audio o video para que se reproduzca 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 se reproduzcan automáticamente nuevas transmisiones asignadas al elemento. El atributo playsinline permite reproducir videos intercalados, 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>