Começar a usar dispositivos de mídia

No desenvolvimento para a Web, o padrão WebRTC fornece APIs para acessar câmeras e microfones conectados ao computador ou smartphone. Esses dispositivos geralmente são chamados de dispositivos de mídia e podem ser acessados com JavaScript usando o objeto navigator.mediaDevices, que implementa a interface MediaDevices. Nesse objeto, podemos enumerar todos os dispositivos conectados, detectar mudanças quando um dispositivo estiver conectado ou desconectado e abri-lo para recuperar um Media Stream (veja abaixo).

A maneira mais comum de usar isso é pela função getUserMedia(), que retorna uma promessa que será resolvida como um MediaStream para os dispositivos de mídia correspondentes. Essa função usa um único objeto MediaStreamConstraints que especifica os requisitos que temos. Por exemplo, para simplesmente abrir o microfone e a câmera padrão, faremos o seguinte.

Como usar promessas

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

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

A chamada para getUserMedia() acionará uma solicitação de permissão. Se o usuário aceitar a permissão, a promessa será resolvida com um MediaStream que contém um vídeo e uma faixa de áudio. Se a permissão for negada, uma PermissionDeniedError será gerada. Caso não haja dispositivos correspondentes conectados, uma NotFoundError será gerada.

A referência completa da API para a interface MediaDevices está disponível em documentos da Web da MDN.

Como consultar dispositivos de mídia

Em um aplicativo mais complexo, é provável que você queira verificar todas as câmeras e os microfones conectados e fornecer feedback adequado ao usuário. Para isso, chame a função enumerateDevices(). Isso retornará uma promessa que é resolvida em uma matriz de MediaDevicesInfo que descreve cada dispositivo de mídia conhecido. Podemos usá-la para apresentar uma IU ao usuário, que pode escolher a que preferir. Cada MediaDevicesInfo contém uma propriedade chamada kind com o valor audioinput, audiooutput ou videoinput, indicando que tipo de dispositivo de mídia ele é.

Como usar promessas

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

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

Detectar as mudanças nos dispositivos

A maioria dos computadores é compatível com a conexão de vários dispositivos durante o tempo de execução. Pode ser uma webcam conectada por USB, um fone de ouvido Bluetooth ou um conjunto de alto-falantes externos. Para oferecer compatibilidade adequada com isso, um app da Web precisa detectar as mudanças de dispositivos de mídia. Isso pode ser feito adicionando um listener a navigator.mediaDevices para o 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);
});

Restrições de mídia

O objeto de restrições, que precisa implementar a interface MediaStreamConstraints, transmitido como um parâmetro para getUserMedia(), permite abrir um dispositivo de mídia que corresponda a um determinado requisito. Esse requisito pode ser muito flexível (áudio e/ou vídeo) ou muito específico (resolução mínima da câmera ou um ID de dispositivo exato). Recomenda-se que os aplicativos que usam a API getUserMedia() verifiquem primeiro os dispositivos existentes e, em seguida, especifique uma restrição que corresponde exatamente ao dispositivo que usa a restrição deviceId. Se possível, os dispositivos também serão configurados de acordo com as restrições. Podemos ativar o cancelamento de eco nos microfones ou definir uma largura e altura específicas ou mínimas para o vídeo da câmera.

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

A documentação completa da interface MediaStreamConstraints pode ser encontrada nos documentos da Web da MDN.

Reprodução local

Depois que um dispositivo de mídia for aberto e tiver um MediaStream disponível, poderemos atribuí-lo a um elemento de vídeo ou áudio para reproduzir o stream localmente.

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

O HTML necessário para um elemento de vídeo típico usado com getUserMedia() geralmente terá os atributos autoplay e playsinline. O atributo autoplay fará com que novos streams atribuídos ao elemento sejam exibidos automaticamente. O atributo playsinline permite que o vídeo seja exibido inline, em vez de apenas na tela completa, em determinados navegadores para dispositivos móveis. Também é recomendado usar controls="false" para transmissões ao vivo, a menos que o usuário possa pausá-las.

<html>
<head><title>Local video playback</title></head>
<body>
    <video id="localVideo" autoplay playsinline controls="false"/>
</body>
</html>