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:
feather.replace()
: essa chamada de método cria uma instância de feather, que é um ícone definido para o desenvolvimento Web.- 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. getCameraSelection
: essa função chama o métodoenumerateDevices
. Em seguida, você filtra a matriz gerada a partir daPromise
resolvida e seleciona os dispositivos de entrada de vídeo. A partir dos resultados filtrados, você cria<option>
para o elemento<select>
.- Chamar o método
getUserMedia
acontece dentro do ouvinteonclick
do botãoplay
. Aqui, você irá verificar se esse método é suportado pelo navegador do usuário antes de iniciar a transmissão. - Em seguida, você irá chamar a função
startStream
que recebe um argumentoconstraints
. Ela chama o métodogetUserMedia
com asconstraints
fornecidas. OhandleStream
é chamado usando a transmissão daPromise
resolvida. Esse método define a transmissão retornada para osrcObject
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.