Introdução

Juntamente com o HTML5 foram introduzidas as APIs com acesso aos dispositivos de hardware, incluindo a API MediaDevices. Essa API fornece acesso aos dispositivos de entrada de mídia como áudio e vídeo.

Com a ajuda dessa API, os desenvolvedores podem acessar dispositivos de áudio e vídeo para transmitir e exibir feeds de vídeo ao vivo no navegador. Neste tutorial, você irá acessar o feed de vídeo do dispositivo do usuário e exibi-lo no navegador usando o método getUserMedia.

A API getUserMedia utiliza os dispositivos de entrada de mídia para produzir um MediaStream (transmissão de mídia). Esse MediaStream contém os tipos de mídia solicitados, seja áudio ou vídeo. Ao usar a transmissão retornada da API, é possível exibir os feeds de vídeo no navegador, o que é útil na comunicação em tempo real no navegador.

Quando usado em conjunto com a API de gravação do MediaStream, é possível gravar e armazenar os dados de mídia capturados no navegador. Essa API só funciona em origens seguras assim como as APIs recentemente introduzidas, mas também funciona no localhost e URLs de arquivos.

Pré-requisitos

com illustration for: Pré-requisitos

Este tutorial irá explicar inicialmente alguns conceitos e demonstrar exemplos com o Codepen. No passo final, você irá criar um feed de vídeo funcional para o navegador.

Passo 1 — Verificando o suporte de dispositivos

Primeiro, você verá como verificar se o navegador do usuário oferece suporte à API mediaDevices. Essa API existe dentra da interface do navegador e contém o estado atual e a identidade do agente do usuário. A verificação é realizada com o código a seguir que pode ser colado no Codepen:

				
					
if ('mediaDevices' in navigator && 'getUserMedia' in navigator.mediaDevices) {

 console.log("Let's get this party started")

}

				
			

Primeiro, ele verifica se a API mediaDevices existe dentro de navigator (navegador) e então verifica se a API getUserMedia está disponível dentro dos mediaDevices. Se o comando retorna true, podemos iniciar.

Passo 2 — Solicitando a permissão do usuário

Depois de confirmar que o navegador dá suporte à getUserMedia, é necessário solicitar a permissão para utilizar os dispositivos de entrada de mídia no agente do usuário. Normalmente, depois que o usuário concede a permissão, uma Promise é retornada e resolve para uma transmissão de mídia. Essa Promise não é retornada quando a permissão é negada pelo usuário, bloqueando o acesso a esses dispositivos.

Cole a linha a seguir no Codepen para solicitar a permissão:

				
					
navigator.mediaDevices.getUserMedia({video: true})

				
			

O objeto fornecido como um argumento para o método getUserMedia chama-se constraints (restrições). Ele determina quais os dispositivos de entrada de mídia os quais você está solicitando permissão para acessar. Por exemplo, se o objeto contém audio: true, o usuário será solicitado a conceder acesso ao dispositivo de entrada de áudio.

Passo 3 — Compreendendo as restrições de mídia

Esta seção irá abordar o conceito geral de _contraints_. O objeto constraints é um objeto MediaStreamConstraints que especifica os tipos de mídia para solicitar e os requisitos de cada tipo de mídia. É possível especificar os requisitos para a transmissão solicitada usando o objeto constraints, como a resolução da transmissão a ser usada (front, back).

É necessário especificar audio ou video ao fazer a solicitação. Um NotFoundError será retornado caso os tipos de mídia solicitados não possam ser encontrados no navegador do usuário.

Se você pretende solicitar uma transmissão de vídeo de resolução 1280 x 720, atualize o objeto constraints para que fique assim:

				
					
{

 video: {

 width: 1280,

 height: 720,

 }

}

				
			

Com essa atualização, o navegador tentará utilizar as configurações de qualidade especificadas para a transmissão. Se o dispositivo de vídeo não puder entregar essa resolução, o navegador retornará outras resoluções disponíveis.

Para garantir que o navegador retorne uma resolução que não seja inferior àquela fornecida, será necessário utilizar a propriedade min. Aqui está como atualizar o objeto constraints para incluir a propriedade min:

				
					
{

 video: {

 width: {

 min: 1280,

 },

 height: {

 min: 720,

 }

 }

}

				
			

Isso irá garantir que a resolução da transmissão retornada seja de pelo menos 1280 x 720. Caso esse requisito mínimo não possa ser atendido, a promessa será rejeitada com um OverconstrainedError.

Em alguns casos, você pode ter a preocupação de salvar dados e precisa que a transmissão não ultrapasse uma determinada resolução. Isso pode ser útil nos casos em que o usuário esteja em um plano limitado. Para habilitar essa funcionalidade, atualize o objeto de restrições para que contenha um campo max:

				
					
{

 video: {

 width: {

 min: 1280,

 max: 1920,

 },

 height: {

 min: 720,

 max: 1080

 }

 }

}

				
			

Com essas configurações, o navegador irá garantir que a transmissão de retorno não tenha resolução inferior a 1280 x 720 nem superior a 1920 x 1080.

Outros termos que podem ser utilizados incluem exact e ideal. A configuração ideal é normalmente usada juntamente com as propriedades min e max para encontrar a melhor resolução possível, o mais perto dos valores ideais fornecidos.

Atualize as restrições para incluir a palavra-chave ideal:

				
					
{

 video: {

 width: {

 min: 1280,

 ideal: 1920,

 max: 2560,

 },

 height: {

 min: 720,

 ideal: 1080,

 max: 1440

 }

 }

}

				
			

Para fazer o navegador usar a câmera frontal ou traseira (em portáteis) nos dispositivos, especifique uma propriedade facingMode no objeto video:

				
					
{

 video: {

 width: {

 min: 1280,

 ideal: 1920,

 max: 2560,

 },

 height: {

 min: 720,

 ideal: 1080,

 max: 1440

 },

 facingMode: 'user'

 }

}

				
			

Essa configuração irá utilizar a câmera frontal o tempo todo em todos os dispositivos. Para utilizar a câmera traseira em dispositivos móveis, altere a propriedade facingMode para environment.

				
					
{

 video: {

 ...

 facingMode: {

 exact: 'environment'

 }

 }

}

				
			

Passo 4 — Usando o método enumerateDevices

Quando o método enumerateDevices é chamado, ele retorna todos os dispositivos de entrada de mídia disponíveis no PC do usuário.

Com esse método, é possível oferecer opções ao usuário sobre qual dispositivo de entrada de mídia usar para a transmissão de conteúdo de áudio ou vídeo. Esse método retorna uma Promise resolvida para uma matriz MediaDeviceInfo contendo informações sobre cada dispositivo.

Um exemplo de como utilizar esse método é mostrado no trecho abaixo:

				
					
async function getDevices() {

 const devices = await navigator.mediaDevices.enumerateDevices();

}

				
			

Uma amostra de resposta para cada um dos dispositivos se pareceria com a seguinte:

				
					
{

 deviceId: "23e77f76e308d9b56cad920fe36883f30239491b8952ae36603c650fd5d8fbgj",

 groupId: "e0be8445bd846722962662d91c9eb04ia624aa42c2ca7c8e876187d1db3a3875",

 kind: "audiooutput",

 label: "",

}

				
			

Nota: um rótulo não será retornado a menos que uma transmissão esteja disponível, ou se o usuário tenha concedido permissões de acesso ao dispositivo.

Passo 5 — Exibindo a transmissão de vídeo no navegador

Até aqui, você passou pelo processo de solicitar e ganhar acesso aos dispositivos de mídia, configurou restrições para incluir as resoluções necessárias e selecionou a câmera que será utilizada para gravar o vídeo.

Depois de todos esses passos, você irá pelo menos querer ver se a transmissão está sendo realizada com base nas configurações definidas. Para garantir isso, o elemento <video> será usado para exibir a transmissão de vídeo no navegador.

Como mencionado anteriormente, o método getUserMedia retorna uma Promise que pode ser resolvida para uma transmissão. A transmissão retornada pode ser convertida em uma URL de objeto usando o método createObjectURL. Essa URL será definida como uma fonte de vídeo.

Você irá criar uma pequena demonstração na qual deixamos o usuário escolher de sua lista de dispositivos de vídeo disponíveis usando o método enumerateDevices.

Este é um método navigator.mediaDevices. Ele lista os dispositivos de mídia disponíveis, como microfones e câmeras. Depois retorna uma Promise resolvida para uma matriz de objetos detalhando os dispositivos de mídia disponíveis.

Crie um arquivo index.html e atualize o conteúdo com o código abaixo:

				
					
[label index.html]

&lt;!doctype html&gt;

&lt;html lang="en"&gt;

&lt;head&gt;

 &lt;meta charset="UTF-8"&gt;

 &lt;meta name="viewport"

 content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"&gt;

 &lt;meta http-equiv="X-UA-Compatible" content="ie=edge"&gt;

 &lt;link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css"&gt;

 &lt;link rel="stylesheet" href="style.css"&gt;

 &lt;title&gt;Document&lt;/title&gt;

&lt;/head&gt;

&lt;body&gt;

&lt;div class="display-cover"&gt;

 &lt;video autoplay&gt;&lt;/video&gt;

 &lt;canvas class="d-none"&gt;&lt;/canvas&gt;



 &lt;div class="video-options"&gt;

 &lt;select name="" id="" class="custom-select"&gt;

 &lt;option value=""&gt;Select camera&lt;/option&gt;

 &lt;/select&gt;

 &lt;/div&gt;



 &lt;img class="screenshot-image d-none" alt=""&gt;



 &lt;div class="controls"&gt;

 &lt;button class="btn btn-danger play" title="Play"&gt;&lt;i data-feather="play-circle"&gt;&lt;/i&gt;&lt;/button&gt;

 &lt;button class="btn btn-info pause d-none" title="Pause"&gt;&lt;i data-feather="pause"&gt;&lt;/i&gt;&lt;/button&gt;

 &lt;button class="btn btn-outline-success screenshot d-none" title="ScreenShot"&gt;&lt;i data-feather="image"&gt;&lt;/i&gt;&lt;/button&gt;

 &lt;/div&gt;

&lt;/div&gt;



&lt;script src="https://unpkg.com/feather-icons"&gt;&lt;/script&gt;

&lt;script src="script.js"&gt;&lt;/script&gt;

&lt;/body&gt;

&lt;/html&gt;

				
			

No trecho acima, foram configurados os elementos que serão necessários e alguns controles para o vídeo. Também foi incluído um botão para tirar capturas de tela do feed de vídeo atual.

Agora, vamos adicionar um pouco de estilo a esses componentes.

Crie um arquivo style.css e copie os estilos a seguir nele. O Bootstrap foi incluído para reduzir a quantidade de CSS que você precisará escrever para que os componentes sejam iniciados.

				
					
[label style.css]

.screenshot-image {

 width: 150px;

 height: 90px;

 border-radius: 4px;

 border: 2px solid whitesmoke;

 box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.1);

 position: absolute;

 bottom: 5px;

 left: 10px;

 background: white;

}



.display-cover {

 display: flex;

 justify-content: center;

 align-items: center;

 width: 70%;

 margin: 5% auto;

 position: relative;

}



video {

 width: 100%;

 background: rgba(0, 0, 0, 0.2);

}



.video-options {

 position: absolute;

 left: 20px;

 top: 30px;

}



.controls {

 position: absolute;

 right: 20px;

 top: 20px;

 display: flex;

}



.controls &gt; button {

 width: 45px;

 height: 45px;

 text-align: center;

 border-radius: 100%;

 margin: 0 6px;

 background: transparent;

}



.controls &gt; button:hover svg {

 color: white !important;

}



@media (min-width: 300px) and (max-width: 400px) {

 .controls {

 flex-direction: column;

 }



 .controls button {

 margin: 5px 0 !important;

 }

}



.controls &gt; button &gt; svg {

 height: 20px;

 width: 18px;

 text-align: center;

 margin: 0 auto;

 padding: 0;

}



.controls button:nth-child(1) {

 border: 2px solid #D2002E;

}



.controls button:nth-child(1) svg {

 color: #D2002E;

}



.controls button:nth-child(2) {

 border: 2px solid #008496;

}



.controls button:nth-child(2) svg {

 color: #008496;

}



.controls button:nth-child(3) {

 border: 2px solid #00B541;

}



.controls button:nth-child(3) svg {

 color: #00B541;

}



.controls &gt; button {

 width: 45px;

 height: 45px;

 text-align: center;

 border-radius: 100%;

 margin: 0 6px;

 background: transparent;

}



.controls &gt; button:hover svg {

 color: white;

}

				
			

O próximo passo é adicionar funcionalidade à demonstração. Usando o método enumerateDevices, você irá obter os dispositivos de vídeo disponíveis e os definirá como opções dentro do elemento selecionado. Crie um arquivo chamado script.js e atualize-o com o seguinte trecho:

				
					
[label script.js]

feather.replace();



const controls = document.querySelector('.controls');

const cameraOptions = document.querySelector('.video-options&gt;select');

const video = document.querySelector('video');

const canvas = document.querySelector('canvas');

const screenshotImage = document.querySelector('img');

const buttons = [...controls.querySelectorAll('button')];

let streamStarted = false;



const [play, pause, screenshot] = buttons;



const constraints = {

 video: {

 width: {

 min: 1280,

 ideal: 1920,

 max: 2560,

 },

 height: {

 min: 720,

 ideal: 1080,

 max: 1440

 },

 }

};



const getCameraSelection = async () =&gt; {

 const devices = await navigator.mediaDevices.enumerateDevices();

 const videoDevices = devices.filter(device =&gt; device.kind === 'videoinput');

 const options = videoDevices.map(videoDevice =&gt; {

 return `&lt;option value="${videoDevice.deviceId}"&gt;${videoDevice.label}&lt;/option&gt;`;

 });

 cameraOptions.innerHTML = options.join('');

};



play.onclick = () =&gt; {

 if (streamStarted) {

 video.play();

 play.classList.add('d-none');

 pause.classList.remove('d-none');

 return;

 }

 if ('mediaDevices' in navigator &amp;&amp; navigator.mediaDevices.getUserMedia) {

 const updatedConstraints = {

 ...constraints,

 deviceId: {

 exact: cameraOptions.value

 }

 };

 startStream(updatedConstraints);

 }

};



const startStream = async (constraints) =&gt; {

 const stream = await navigator.mediaDevices.getUserMedia(constraints);

 handleStream(stream);

};



const handleStream = (stream) =&gt; {

 video.srcObject = stream;

 play.classList.add('d-none');

 pause.classList.remove('d-none');

 screenshot.classList.remove('d-none');

 streamStarted = true;

};



getCameraSelection();

				
			

No trecho acima, algumas coisas estão acontecendo. Vamos dividi-las:

  1. feather.replace(): essa chamada de método cria uma instância de feather, que é um ícone definido para o desenvolvimento Web.
  1. A variável constraints contém a configuração inicial para a transmissão. Ela será estendida para incluir o dispositivo de mídia escolhido pelo usuário.
  1. getCameraSelection: essa função chama o método enumerateDevices. Em seguida, você filtra a matriz gerada a partir da Promise resolvida e seleciona os dispositivos de entrada de vídeo. A partir dos resultados filtrados, você cria <option> para o elemento <select>.
  1. Chamar o método getUserMedia acontece dentro do ouvinte onclick do botão play. Aqui, você irá verificar se esse método é suportado pelo navegador do usuário antes de iniciar a transmissão.
  1. Em seguida, você irá chamar a função startStream que recebe um argumento constraints. Ela chama o método getUserMedia com as constraints fornecidas. O handleStream é chamado usando a transmissão da Promise resolvida. Esse método define a transmissão retornada para o srcObject do elemento de vídeo.

Em seguida, você irá adicionar um listener de clique aos controles dos botões na página para pause, stop e tirar screenshots. Além disso, você irá adicionar um listener ao elemento <select> para atualizar as restrições da transmissão com o dispositivo de vídeo selecionado.

Atualize o arquivo script.js com o código abaixo:

				
					
[label script.js]

...

cameraOptions.onchange = () =&gt; {

 const updatedConstraints = {

 ...constraints,

 deviceId: {

 exact: cameraOptions.value

 }

 };

 startStream(updatedConstraints);

};



const pauseStream = () =&gt; {

 video.pause();

 play.classList.remove('d-none');

 pause.classList.add('d-none');

};



const doScreenshot = () =&gt; {

 canvas.width = video.videoWidth;

 canvas.height = video.videoHeight;

 canvas.getContext('2d').drawImage(video, 0, 0);

 screenshotImage.src = canvas.toDataURL('image/webp');

 screenshotImage.classList.remove('d-none');

};



pause.onclick = pauseStream;

screenshot.onclick = doScreenshot;

				
			

Agora, quando ao se abrir o arquivo index.html no navegador, clicar no botão Play irá iniciar a transmissão.

Aqui está uma demonstração completa:

Conclusão

Esse tutorial introduziu a API getUserMedia. É uma adição interessante ao HTML5 que facilita o processo de captura de mídia na Web.

A API recebe um parâmetro (constraints) que pode ser usado para configurar o acesso aos dispositivos de entrada de áudio e vídeo. Ela também pode ser usada para especificar a resolução de vídeo necessária para o seu aplicativo.

É possível estender a demonstração ainda mais para dar ao usuário uma opção para salvar as capturas de tela feitas, bem como gravar e armazenar dados de vídeo e áudio com a ajuda da API MediaStream Recording.