Como gravar um 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 dinâmica e inline completa ou pode ser delegada a outro app no dispositivo do usuário.

Comece de forma simples e progressiva

A maneira mais fácil de fazer isso é pedir ao usuário um arquivo pré-gravado. Para fazer 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 receber o arquivo diretamente da câmera.

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

Esse método funciona em todas as plataformas. No computador, o usuário vai ser solicitado a fazer upload de um arquivo do sistema de arquivos (ignorando o atributo capture). No Safari no iOS, ele vai abrir o app 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 escolhe em qual app gravar o vídeo antes de enviar 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 oferecer suporte à opção ou se 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ê precisa de alguma forma acessar os dados do arquivo. É possível ter acesso rápido anexando um evento onchange ao elemento de entrada e lendo 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 ter acesso ao 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
  • Fazer o download no dispositivo do usuário
  • Fazer upload para um servidor anexando a um XMLHttpRequest
  • 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 onibpresente, é a opção menos atraente. Queremos ter acesso à câmera e oferecer uma boa experiência diretamente na página.

Acessar a câmera de forma interativa

Os navegadores modernos podem ter uma linha direta para a câmera, permitindo que criemos experiências totalmente integradas à página da Web, e o usuário nunca sai do navegador.

Adquirir acesso à câmera

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

Se bem-sucedida, a API vai retornar um Stream que conterá os dados da câmera ou do microfone. Em seguida, podemos anexar o elemento a um <video>, a um stream do WebRTC ou salvar usando a API MediaRecorder.

Para receber dados da câmera, definimos 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 você 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. Tudo o que podemos fazer é pegar os dados do vídeo e reproduzir.

Acessar os dados brutos da câmera

Para acessar os dados brutos de vídeo da câmera, você pode desenhar cada frame em uma <canvas> e manipular 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 a 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, você precisa redesenhar o vídeo na tela toda vez.

Para saber mais, consulte nosso 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, que pode ser usado para salvar 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, assim que você chamar getUserMedia, o navegador vai pedir ao usuário para conceder permissão ao site para usar a câmera.

Os usuários odeiam receber solicitações de acesso a dispositivos poderosos na máquina e frequentemente bloqueiam a solicitação ou a ignoram se não entenderam 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 o acesso, ele não será solicitado novamente. No entanto, se ele rejeitar o acesso, não será possível pedir permissão para o usuário novamente.

Usar a API de permissões 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 e fazer com que o usuário conceda 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'} ao 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 será solicitado 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 conferir 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