Tutorial

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

Published on November 12, 2020
author

Chris Nwamba

Português
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

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:

https://codepen.io/chrisbeast/pen/ebYwpX

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.

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about us


About the authors
Default avatar
Chris Nwamba

author

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
Leave a comment


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Featured on Community

Get our biweekly newsletter

Sign up for Infrastructure as a Newsletter.

Hollie's Hub for Good

Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.

Become a contributor

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

Welcome to the developer cloud

DigitalOcean makes it simple to launch in the cloud and scale up as you grow — whether you're running one virtual machine or ten thousand.

Learn more
DigitalOcean Cloud Control Panel