Hoje em dia todo computador, notebook ou smartphone tem uma (ou mais) câmera(s).
Antigamente, a única maneira de aproveitar essas câmeras em uma aplicação Web era usando Flash.
Mas, hoje em dia, podemos tirar fotos com JS. Para isso, vamos utilizar o WebRTC.
WebRTC é um padrão Web que define protocolos e APIs em JS para enviar vídeo, áudio e dados entre navegadores de maneira peer-to-peer e em tempo real. Está disponível no Chrome para Desktop e Android, Firefox, Opera e Edge.
Pra começar, um <video> e um <canvas>
Precisamos de uma estrutura mínima no nosso HTML.
A tag <video>
com a propriedade autoplay
, ficará responsável por mostrar para o usuário as imagens obtidas da câmera.
Para guardar a imagem da foto, vamos usar um <canvas>
, que começara invisível.
Finalmente, precisaremos de um <button>
para que o usuário informe o momento em que deve ser tirada a foto a partir do vídeo.
Os três elementos mencionados ficarão dentro de uma <div>
.
<div class="camera"> <video id="video" class="foto" autoplay>Vídeo não disponível.</video> <canvas id="canvas" class="foto" style="display: none;"></canvas> <button id="tira-foto">Tirar foto</button> </div>
Um pouquinho de css
Para posicionar os elementos e definir uma borda, teremos um pequeno trecho de css:
.camera { width: 340px; display: flex; flex-direction: column; align-items: center; padding: 0.5em; } .foto { width: 320px; height: 240px; border: 1px solid black; margin: 1em; }
Mude o CSS à vontade, para deixar mais estiloso!
Mostrando o vídeo
No nosso código JS, obteremos o elemento cujo id
é video
:
var video = document.querySelector('#video');
Para obter as imagens da câmera, precisamos usar o método getUserMedia
do navigator
.
Uma coisa chata é que a API ainda não está consolidada. Por isso, o getUserMedia
tem prefixos diferentes para cada navegador:
navigator.webkitGetUserMedia
– para o Chrome e Operanavigator.mozGetUserMedia
– para o Firefoxnavigator.msGetUserMedia
– para o Edge
Testaremos o getUserMedia
correto, armazendo-o em uma variável e ajustando o this
através de um bind
com o objeto navigator
:
var getUserMedia = (navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia).bind(navigator);
Ao chamar o getUserMedia
, devemos passar 3 parâmetros:
- um objeto de configuração que diz se deve ser obtido vídeo ou áudio da câmera, entre outros detalhes
- uma função chamada quando houve sucesso ao obter mídia da câmera. Nessa função, obtemos um
MediaStream
, que devemos passar para a propriedadesrcObject
do nosso elementovideo
. Guardaremos oMediaStream
em uma variável, para uso posterior. - uma função chamada em caso de erro
var configuracaoMedia = {video: true, audio: false}; var mediaStream; function iniciaVideo (stream) { video.srcObject = stream; mediaStream = stream; } function trataErroMedia (erro) { console.error('Erro: ' + erro); } getUserMedia(configuracaoMedia, iniciaVideo, trataErroMedia);
O resultado até aqui pode ser encontrado em:
https://jsfiddle.net/alexandreaquiles/xqhLL2wm/1/
Obtendo uma foto
Devemos obter o canvas e o botão a partir de seus ids:
var canvas = document.querySelector('#canvas'); var botaoTiraFoto = document.querySelector('#tira-foto');
Precisamos registrar um tratador do evento de clique no botão.
botaoTiraFoto.addEventListener('click', function (e) { //tratamento aqui... });
Dentro da função de tratamento do clique, devemos obter o contexto 2D a partir do canvas e utilizá-lo para desenhar uma imagem a partir do vídeo. Nesse momento, a foto é tirada.
Podemos obter os dados através do método toDataURL
do canvas
.
botaoTiraFoto.addEventListener('click', function (e) { canvas.getContext('2d').drawImage(video, 0, 0, 320, 240); var dados = canvas.toDataURL('image/png'); //fazer algo com os dados... });
O método toDataURL
retorna uma Data URI, uma maneira de representar imagens e outros tipos de dados como um texto. Por exemplo, um .png com um pontinho preto pode ser representado como: data:image/png;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=
Podemos enviar essa Data URI para o servidor com uma chamada AJAX.
Do lado do servidor, você pode gravar a Data URI no BD para uso posterior como src
de um <img>
ou ainda transformá-lo em um arquivo.
Em PHP, usaríamos
str_replace
,base64_decode
efile_put_contents
para salvar a imagem em um arquivo no servidor.
Você pode testar o código até essa parte em:
https://jsfiddle.net/alexandreaquiles/xqhLL2wm/1/
Mostrando a foto e escondendo o vídeo
Quando o usuário clicar o botão, é interessante mostrarmos o <canvas>
que tem a imagem da foto tirada e, em seguida, escondermos o vídeo da câmera.
botaoTiraFoto.addEventListener('click', function (e) { //código omitido... video.style.display = 'none'; canvas.style.display = ''; });
Ao exibirmos a imagem, percebemos que há uma distorção. Para resolver, é importante fixar a altura e largura do <canvas>
:
canvas.width = 320; canvas.height = 240;
A câmera continuará filmando, mas a imagem ficará parada, sendo alterada só quando o usuário clicar no botão.
Você pode acompanhar o resultado em:
https://jsfiddle.net/alexandreaquiles/xqhLL2wm/2/
Parando a câmera ao obter foto
Para suspender a filmagem logo depois do clique no botão, podemos usar o seguinte código:
mediaStream.getVideoTracks().forEach(function (media) { media.stop(); });
É interessante alternar entre filmagem com a câmera e a exibição da foto tirada. Para isso, alternamos entre exibição do <video>
e do <canvas>
. Além disso, alternamos entre parar e voltar com o funcionamento da câmera.
botaoTiraFoto.addEventListener('click', function (e) { if (mediaStream) { //código omitido... mediaStream.getVideoTracks().forEach(function (media) { media.stop(); }); video.style.display = 'none'; canvas.style.display = ''; mediaStream = null; } else { getUserMedia(configuracaoMedia, iniciaVideo, trataErroMedia); video.style.display = ''; canvas.style.display = 'none'; }
O código até aqui está em:
https://jsfiddle.net/alexandreaquiles/xqhLL2wm/3/
Para funcionar no Chrome do Android
Com o código anterior, ocorre um bug quando acessado pelo Chrome do Android: é mostrada uma imagem preta no vídeo.
Para corrigí-lo, é necessário fixar as propriedades opcionais maxWidth
e minWidth
na configuração de getUserMedia
:
var configuracaoMedia = { video: { optional: [ { maxWidth: 320}, { maxHeight: 240} ] }, audio: false };
O código final pode ser encontrado em:
https://jsfiddle.net/alexandreaquiles/xqhLL2wm/4/
Consideração importante: tem que ser HTTPS
Para que o código acima funcione no Chrome e Opera quando publicado em um servidor real, não em sua máquina local, é preciso que seja usado HTTPS.
Para o Firefox não há essa restrição.