Gravação de vídeo do usuário

Mat Scales

Muitos navegadores agora têm a capacidade de acessar a entrada de vídeo e áudio do usuário. No entanto, dependendo do navegador, pode ser uma experiência totalmente dinâmica e inline ou pode ser delegada a outro app no dispositivo do usuário.

Comece de forma simples e progressiva

A maneira mais fácil é pedir ao usuário um arquivo pré-gravado. Para isso, crie um elemento de entrada de arquivo simples e adicione um filtro accept que indique que só podemos aceitar arquivos de vídeo e um atributo capture que indique que queremos recebê-lo diretamente da câmera.

<input type="file" accept="video/*" capture />

Esse método funciona em todas as plataformas. No computador, ele vai solicitar que o usuário faça upload de um arquivo do sistema de arquivos (ignorando o atributo capture). No Safari no iOS, ele abre o app de câmera, permitindo que você grave um vídeo e o envie de volta para a página da Web. No Android, o usuário pode escolher qual app usar para gravar o vídeo antes de enviá-lo de volta para a página da Web.

Muitos dispositivos móveis têm mais de uma câmera. Se você tiver uma preferência, defina o atributo capture como user, se quiser a câmera voltada para o usuário, ou environment, se quiser a câmera voltada para fora.

<input type="file" accept="video/*" capture="user" />
<input type="file" accept="video/*" capture="environment" />

Essa é apenas uma dica. Se o navegador não for compatível com a opção ou o tipo de câmera solicitado não estiver disponível, o navegador poderá escolher outra câmera.

Depois que o usuário terminar a gravação e voltar ao site, você precisará acessar os dados do arquivo. Para ter acesso rápido, anexe um evento onchange ao elemento de entrada e leia a propriedade files do objeto de evento.

<input type="file" accept="video/*" capture="camera" id="recorder" />
<video id="player" controls></video>
<script>
  var recorder = document.getElementById('recorder');
  var player = document.getElementById('player');

  recorder.addEventListener('change', function (e) {
    var file = e.target.files[0];
    // Do something with the video file.
    player.src = URL.createObjectURL(file);
  });
</script>

Depois de acessar o arquivo, você pode fazer o que quiser com ele. Por exemplo, você pode:

  • Anexá-lo diretamente a um elemento <video> para que ele possa ser reproduzido.
  • Baixar no dispositivo do usuário
  • Anexe o arquivo a um XMLHttpRequest para fazer upload dele em um servidor.
  • Desenhar os frames em uma tela e aplicar filtros a ela

Embora o uso do método de elemento de entrada para acessar dados de vídeo seja onipresente, é a opção menos atraente. Queremos acessar a câmera e oferecer uma boa experiência diretamente na página.

Acessar a câmera de forma interativa

Os navegadores mais recentes podem ter uma linha direta com a câmera, o que nos permite criar experiências totalmente integradas à página da Web, e o usuário nunca sai do navegador.

Acessar a câmera

Podemos acessar diretamente a câmera usando uma API na especificação WebRTC chamada getUserMedia(). getUserMedia() vai solicitar ao usuário acesso aos microfones e câmeras conectados.

Se for bem-sucedida, a API vai retornar um Stream que vai conter os dados da câmera ou do microfone, que podem ser anexados a um elemento <video>, a um stream do WebRTC ou salvos usando a API MediaRecorder.

Para receber dados da câmera, basta definir video: true no objeto de restrições que é transmitido para a API getUserMedia().

<video id="player" controls></video>
<script>
  var player = document.getElementById('player');

  var handleSuccess = function (stream) {
    player.srcObject = stream;
  };

  navigator.mediaDevices
    .getUserMedia({audio: true, video: true})
    .then(handleSuccess);
</script>

Se quiser escolher uma câmera específica, primeiro enumere as câmeras disponíveis.

navigator.mediaDevices.enumerateDevices().then((devices) => {
  devices = devices.filter((d) => d.kind === 'videoinput');
});

Em seguida, transmita o deviceId que você quer usar ao chamar getUserMedia.

navigator.mediaDevices.getUserMedia({
  audio: true,
  video: {
    deviceId: devices[0].deviceId,
  },
});

Por si só, isso não é muito útil. Só podemos pegar os dados do vídeo e reproduzi-los.

Acessar os dados brutos da câmera

Para acessar os dados de vídeo brutos da câmera, desenhe cada frame em um <canvas> e manipule os pixels diretamente.

Para uma tela 2D, use o método drawImage do contexto para desenhar o frame atual de um elemento <video> na tela.

context.drawImage(myVideoElement, 0, 0);

Com uma tela WebGL, é possível usar um elemento <video> como origem de uma textura.

gl.texImage2D(
  gl.TEXTURE_2D,
  0,
  gl.RGBA,
  gl.RGBA,
  gl.UNSIGNED_BYTE,
  myVideoElement,
);

Em ambos os casos, o frame atual de um vídeo em reprodução será usado. Para processar vários frames, é necessário redesenhar o vídeo na tela sempre que houver uma mudança.

Saiba mais no artigo sobre como aplicar efeitos em tempo real a imagens e vídeos.

Salvar os dados da câmera

A maneira mais fácil de salvar os dados da câmera é usar a API MediaRecorder.

A API MediaRecorder vai usar o stream criado por getUserMedia e, em seguida, salvar progressivamente os dados do stream no destino de sua preferência.

<a id="download">Download</a>
<button id="stop">Stop</button>
<script>
  let shouldStop = false;
  let stopped = false;
  const downloadLink = document.getElementById('download');
  const stopButton = document.getElementById('stop');

  stopButton.addEventListener('click', function() {
    shouldStop = true;
  })

  var handleSuccess = function(stream) {
    const options = {mimeType: 'video/webm'};
    const recordedChunks = [];
    const mediaRecorder = new MediaRecorder(stream, options);

    mediaRecorder.addEventListener('dataavailable', function(e) {
      if (e.data.size > 0) {
        recordedChunks.push(e.data);
      }

      if(shouldStop === true && stopped === false) {
        mediaRecorder.stop();
        stopped = true;
      }
    });

    mediaRecorder.addEventListener('stop', function() {
      downloadLink.href = URL.createObjectURL(new Blob(recordedChunks));
      downloadLink.download = 'acetest.webm';
    });

    mediaRecorder.start();
  };

  navigator.mediaDevices.getUserMedia({ audio: true, video: true })
      .then(handleSuccess);
</script>

No nosso caso, estamos salvando os dados diretamente em uma matriz que pode ser transformada em um Blob para ser salvo no servidor da Web ou diretamente no armazenamento do dispositivo do usuário.

Pedir permissão para usar a câmera de forma responsável

Se o usuário não tiver concedido acesso à câmera ao seu site, o navegador vai solicitar que ele faça isso assim que você chamar getUserMedia.

Os usuários não gostam de receber solicitações de acesso a dispositivos avançados na máquina e costumam bloquear a solicitação ou ignorá-la se não entenderem o contexto em que ela foi criada. A prática recomendada é pedir acesso à câmera apenas quando for necessário. Depois que o usuário conceder acesso, ele não será solicitado novamente. No entanto, se ele rejeitar o acesso, não será possível solicitar a permissão novamente.

Use a API Permissions para verificar se você já tem acesso

A API getUserMedia não informa se você já tem acesso à câmera. Isso apresenta um problema: para fornecer uma boa interface que faça o usuário conceder acesso à câmera, você precisa pedir acesso à câmera.

Isso pode ser resolvido em alguns navegadores usando a API Permission. A API navigator.permission permite consultar o estado da capacidade de acessar APIs específicas sem precisar solicitar novamente.

Para consultar se você tem acesso à câmera do usuário, transmita {name: 'camera'} para o método de consulta, que vai retornar:

  • granted: o usuário já concedeu acesso à câmera.
  • prompt: o usuário não concedeu acesso a você e vai receber uma solicitação quando você chamar getUserMedia.
  • denied: o sistema ou o usuário bloqueou explicitamente o acesso à câmera, e você não poderá acessá-la.

Agora você pode verificar rapidamente se precisa alterar a interface do usuário para acomodar as ações que o usuário precisa realizar.

navigator.permissions.query({name: 'camera'}).then(function (result) {
  if (result.state == 'granted') {
  } else if (result.state == 'prompt') {
  } else if (result.state == 'denied') {
  }
  result.onchange = function () {};
});

Feedback