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 são normalmente chamados de dispositivos de mídia e podem ser acessados com JavaScript usando o objeto navigator.mediaDevices, que implementa o MediaDevices interface gráfica do usuário. A partir desse objeto, podemos enumerar todos os dispositivos conectados, detectar mudanças no dispositivo (quando um dispositivo é conectado ou desconectado) e abre um dispositivo para recuperar um fluxo de mídia (veja abaixo).

A forma mais comum é pela função getUserMedia(), que retorna uma promessa que se resolve em uma MediaStream para a mídia correspondente dispositivos. Essa função usa um único objeto MediaStreamConstraints que especifica os requisitos que temos. Por exemplo, para simplesmente abrir microfone e 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() vai acionar uma solicitação de permissões. Se o usuário aceitar a permissão, a promessa será resolvida com um MediaStream contendo um vídeo e uma faixa de áudio. Se a permissão for negada, um Uma PermissionDeniedError é gerada. Caso não haja dispositivos correspondentes conectado, uma NotFoundError será gerada.

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

Como consultar dispositivos de mídia

Em um aplicativo mais complexo, provavelmente vamos verificar todos os câmeras e microfones conectados e fornecer o feedback apropriado para o usuário. Para isso, chame a função enumerateDevices(). Isso vai retorna uma promessa que é resolvida em uma matriz de MediaDevicesInfo que descreve para cada dispositivo de mídia conhecido. Podemos usar isso para apresentar uma interface ao usuário eles escolhem o que preferem. Cada MediaDevicesInfo contém uma propriedade chamada kind com o valor audioinput, audiooutput ou videoinput, indicando o tipo de dispositivo de mídia.

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 mudanças nos dispositivos

A maioria dos computadores aceita a conexão de vários dispositivos durante o tempo de execução. Pode ser um webcam conectada por USB, um fone de ouvido Bluetooth ou um conjunto de alto-falantes externos. Em para oferecer um suporte adequado, um aplicativo da Web deve detectar as alterações de dispositivos portáteis de mídia. Isso pode ser feito adicionando um listener 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 MediaStreamConstraints. interface, que transmitimos como um parâmetro para getUserMedia() nos permite abrir uma dispositivo de mídia que corresponde a um determinado requisito. Esse requisito pode ser muito vagamente definida (áudio e/ou vídeo) ou muito específica (mínimo de câmera ou um ID de dispositivo exato). É recomendado que os aplicativos que usam a API getUserMedia() verifica primeiro os dispositivos existentes e, em seguida, especifica um que corresponde ao dispositivo exato usando a restrição deviceId. Se possível, os dispositivos também serão configurados de acordo com as restrições. Qa É possível ativar o cancelamento de eco nos microfones ou definir uma largura específica ou mínima e a altura do vídeo mostrado pela 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 na Web MDN docs.

Reprodução local

Assim que um dispositivo de mídia é aberto e temos um MediaStream disponível, nós pode atribuí-lo a um elemento de vídeo ou áudio para reproduzir a transmissão 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 têm os atributos autoplay e playsinline. O autoplay fará com que os novos streams atribuídos ao elemento sejam reproduzidos automaticamente. O atributo playsinline permite que o vídeo seja reproduzido inline, em vez de somente até o fim. em certos navegadores para dispositivos móveis. Também é recomendável usar controls="false" para transmissões ao vivo, a menos que o usuário consiga pausar para resolvê-los com rapidez.

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