Como acessar as câmeras frontal e traseira com o getUserMedia() do JavaScript

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

  • Um conheciomento básico de JavaScript. Caso seja um iniciante no JavaScript, confiar a série Como programar no JavaScript.

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:

index.html

<!doctype html> <html lang="en"> <head>     <meta charset="UTF-8">     <meta name="viewport"           content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">     <meta http-equiv="X-UA-Compatible" content="ie=edge">     <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">     <link rel="stylesheet" href="style.css">     <title>Document</title> </head> <body> <div class="display-cover">     <video autoplay></video>     <canvas class="d-none"></canvas>      <div class="video-options">         <select name="" id="" class="custom-select">             <option value="">Select camera</option>         </select>     </div>      <img class="screenshot-image d-none" alt="">      <div class="controls">         <button class="btn btn-danger play" title="Play"><i data-feather="play-circle"></i></button>         <button class="btn btn-info pause d-none" title="Pause"><i data-feather="pause"></i></button>         <button class="btn btn-outline-success screenshot d-none" title="ScreenShot"><i data-feather="image"></i></button>     </div> </div>  <script src="https://unpkg.com/feather-icons"></script> <script src="script.js"></script> </body> </html> 

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.

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 > button {     width: 45px;     height: 45px;     text-align: center;     border-radius: 100%;     margin: 0 6px;     background: transparent; }  .controls > 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 > button > 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 > button {     width: 45px;     height: 45px;     text-align: center;     border-radius: 100%;     margin: 0 6px;     background: transparent; }  .controls > 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:

script.js

feather.replace();  const controls = document.querySelector('.controls'); const cameraOptions = document.querySelector('.video-options>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 () => {   const devices = await navigator.mediaDevices.enumerateDevices();   const videoDevices = devices.filter(device => device.kind === 'videoinput');   const options = videoDevices.map(videoDevice => {     return `<option value="${videoDevice.deviceId}">${videoDevice.label}</option>`;   });   cameraOptions.innerHTML = options.join(''); };  play.onclick = () => {   if (streamStarted) {     video.play();     play.classList.add('d-none');     pause.classList.remove('d-none');     return;   }   if ('mediaDevices' in navigator && navigator.mediaDevices.getUserMedia) {     const updatedConstraints = {       ...constraints,       deviceId: {         exact: cameraOptions.value       }     };     startStream(updatedConstraints);   } };  const startStream = async (constraints) => {   const stream = await navigator.mediaDevices.getUserMedia(constraints);   handleStream(stream); };  const handleStream = (stream) => {   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.
  2. 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.
  3. 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>.
  4. 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.
  5. 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:

script.js

... cameraOptions.onchange = () => {   const updatedConstraints = {     ...constraints,     deviceId: {       exact: cameraOptions.value     }   };   startStream(updatedConstraints); };  const pauseStream = () => {   video.pause();   play.classList.remove('d-none');   pause.classList.add('d-none'); };  const doScreenshot = () => {   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.