Como gravar áudio do usuário

Atualmente, muitos navegadores têm o recurso de acessar inserções de dados de áudio e vídeo do usuário. No entanto, dependendo do navegador, essa pode ser uma experiência dinâmica e integrada ou delegada a outro app no dispositivo do usuário.

Comece de forma simples e progressiva

A coisa mais fácil a se fazer é simplesmente solicitar 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. Em computadores, o usuário poderá carregar um arquivo do sistema de arquivos (ignorando o atributo capture). No Safari para iOS, ele vai abrir o app de microfone, permitindo a gravação do áudio e em seguida o envio para a página da Web. No Android, ele vai dar ao usuário a opção de escolher um app para gravar o áudio antes de enviá-lo de volta para a página da Web.

Quando o usuário terminar a gravação e for redirecionado ao site, você precisa conseguir os dados do arquivo de alguma forma. É possível ter acesso rápido anexando um evento onchange ao elemento de entrada e lendo a propriedade files do objeto do 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 que você tiver acesso ao arquivo, poderá fazer o que quiser com ele. Por exemplo, você pode:

  • Anexá-lo diretamente a um elemento <audio> para poder reproduzir
  • Fazer o download no dispositivo do usuário
  • Fazer upload dele em um servidor anexando-o a um XMLHttpRequest
  • Passá-lo pela Web Audio API e aplicar filtros a ele

Embora usar o método do elemento "input" para acessar dados de áudio seja universal, é a opção menos vantajosa. Queremos ter acesso ao microfone e oferecer uma experiência bacana diretamente na página.

Acessar o microfone de forma interativa

Os navegadores modernos podem ter ligação direta com o microfone, permitindo criar experiências totalmente integradas à página da Web, sem que o usuário precise sair do navegador.

Adquirir acesso ao microfone

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

Se receber a autorização, a API retornará um Stream que conterá os dados da câmera ou do microfone. Em seguida, poderemos anexá-los a um elemento <audio>, a um fluxo do WebRTC, a um AudioContext do Web Audio ou salvá-los usando a API MediaRecorder.

Para receber dados do microfone, basta definir audio: true no objeto de restrições que é transmitido à 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 que podemos fazer é pegar os dados de áudio e reproduzi-los.

Acessar os dados brutos do microfone

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

Um dos nós a 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 diversas possibilidades do que fazer com esses dados:

  • Faça o upload direto no servidor.
  • Armazenar localmente
  • Converter para um formato de arquivo dedicado, como WAV, e salvar nos servidores ou localmente

Salvar os dados do microfone

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

A API MediaRecorder vai usar o fluxo criado por getUserMedia e, em seguida, salvar progressivamente os dados que estão no fluxo 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 poderemos transformar em um Blob, que pode ser usado para salvar os dados no nosso servidor da Web ou diretamente no armazenamento do dispositivo do usuário.

Solicitar autorização para usar o microfone com responsabilidade

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

Os usuários odeiam receber solicitações de permissão de acesso a dispositivos importantes do seu aparelho e muitas vezes bloqueiam a solicitação ou a ignoram se não entendem por que a solicitação foi criada. A prática recomendada é só pedir acesso ao microfone na primeira vez em que ele for necessário. Depois que o usuário conceder acesso, ele não vai receber mais solicitações de permissão de acesso. No entanto, se ele recusar o acesso, não será possível solicitar a permissão novamente.

Use 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 é um problema: para oferecer uma interface bacana para fazer o usuário conceder acesso ao microfone a você, você tem que pedir acesso ao microfone.

Esse problema pode ser resolvido em alguns navegadores por meio do uso da Permission API. A API navigator.permission permite consultar o estado da capacidade de acessar APIs específicas sem ter que pedir novamente.

Para verificar se você tem acesso ao microfone do usuário, transmita {name: 'microphone'} no método de consulta. Ele vai retornar:

  • granted: o usuário já deu autorização de acesso ao microfone a você;
  • prompt: o usuário não deu acesso a você e vai receber uma solicitação quando você chamar getUserMedia.
  • denied: o sistema ou o usuário bloqueou explicitamente o acesso ao microfone e você não poderá pedir acesso a ele.

Agora você pode verificar rapidamente se precisa alterar sua interface para oferecer as ações que o usuário 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