Como gravar áudio do usuário

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 é 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 áudio e um atributo capture que indique que queremos receber o áudio diretamente do microfone.

<input type="file" accept="audio/*" 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 de microfone, permitindo que você grave o áudio e o envie de volta para a página da Web. No Android, o usuário poderá escolher qual app usar para gravar o áudio antes de enviá-lo de volta para a página da Web.

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

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

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

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

  • Anexá-lo diretamente a um elemento <audio> para que ele possa ser reproduzido
  • Fazer o download no dispositivo do usuário
  • Faça upload para um servidor anexando-o a um XMLHttpRequest
  • Transmita-o pela API Web Audio e aplique filtros a ele

Embora o uso do método de elemento de entrada para acessar dados de áudio seja onibpresente, é a opção menos atraente. Queremos ter acesso ao microfone e oferecer uma boa experiência diretamente na página.

Acessar o microfone de forma interativa

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

Adquirir acesso ao microfone

É possível acessar o microfone 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 <audio>, a um stream do WebRTC, a um AudioContext do Web Audio ou salvar usando a API MediaRecorder.

Para receber dados do microfone, basta definir audio: true no objeto de restrições que é transmitido para a API getUserMedia().

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

  const handleSuccess = function (stream) {
    if (window.URL) {
      player.srcObject = stream;
    } else {
      player.src = stream;
    }
  };

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

Se você quiser escolher um microfone específico, primeiro enumere os microfones disponíveis.

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

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

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

Por si só, isso não é tão útil. Tudo o que podemos fazer é pegar os dados de áudio e reproduzi-los.

Acessar os dados brutos do microfone

Para acessar os dados brutos do microfone, precisamos usar o fluxo criado por getUserMedia() e, em seguida, usar a API Web Audio para processar os dados. A API Web Audio é uma API simples que recebe fontes de entrada e as conecta a nós que podem processar os dados de áudio (ajustar ganho etc.) e finalmente a um alto-falante para que o usuário possa ouvir.

Um dos nós que você pode conectar é um AudioWorkletNode. Esse nó oferece a capacidade de baixo nível para processamento de áudio personalizado. O processamento de áudio realmente acontece no método de callback process() no AudioWorkletProcessor. Chame essa função para alimentar entradas e parâmetros e buscar saídas.

Confira o Enter Audio Worklet para saber mais.

<script>
  const handleSuccess = async function(stream) {
    const context = new AudioContext();
    const source = context.createMediaStreamSource(stream);

    await context.audioWorklet.addModule("processor.js");
    const worklet = new AudioWorkletNode(context, "worklet-processor");

    source.connect(worklet);
    worklet.connect(context.destination);
  };

  navigator.mediaDevices.getUserMedia({ audio: true, video: false })
      .then(handleSuccess);
</script>
// processor.js
class WorkletProcessor extends AudioWorkletProcessor {
  process(inputs, outputs, parameters) {
    // Do something with the data, e.g. convert it to WAV
    console.log(inputs);
    return true;
  }
}

registerProcessor("worklet-processor", WorkletProcessor);

Os dados armazenados nos buffers são os dados brutos do microfone, e você tem várias opções do que pode fazer com eles:

  • Faça o upload diretamente no servidor
  • Armazenar localmente
  • Converta-o em um formato de arquivo dedicado, como WAV, e salve-o nos seus servidores ou localmente.

Salvar os dados do microfone

A maneira mais fácil de salvar os dados do microfone é usar a API MediaRecorder.

A API MediaRecorder vai usar o stream criado por getUserMedia e, em seguida, salvar progressivamente os dados que estão no stream no destino preferido.

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


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

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

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

    stopButton.addEventListener('click', function() {
      mediaRecorder.stop();
    });

    mediaRecorder.start();
  };

  navigator.mediaDevices.getUserMedia({ audio: true, video: false })
      .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 os dados no servidor da Web ou diretamente no armazenamento do dispositivo do usuário.

Pedir permissão para usar o microfone de forma responsável

Se o usuário não tiver concedido ao seu site acesso ao microfone, no momento em que você chamar getUserMedia, o navegador vai solicitar que o usuário conceda permissão ao microfone.

Os usuários odeiam receber solicitações de acesso a dispositivos poderosos na máquina e com frequência bloqueiam a solicitação ou a ignoram se não entenderem o contexto em que a solicitação foi criada. É recomendável pedir acesso ao microfone 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 a permissão 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 ao microfone. Isso apresenta um problema. Para fornecer uma interface legal e fazer com que o usuário conceda acesso ao microfone, você precisa pedir acesso ao microfone.

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 ao microfone do usuário, transmita {name: 'microphone'} ao método de consulta, que vai retornar:

  • granted: o usuário já concedeu acesso ao microfone.
  • 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 ao microfone, e você não poderá acessá-lo.

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

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

Feedback