Запись звука от пользователя

Многие браузеры теперь имеют возможность доступа к видео- и аудиовходу пользователя. Однако, в зависимости от браузера, это может быть полностью динамический и встроенный интерфейс или его можно делегировать другому приложению на устройстве пользователя.

Начните с простого и постепенно

Самый простой способ — просто попросить пользователя предоставить заранее записанный файл. Сделайте это, создав простой элемент ввода файла и добавив фильтр accept , который указывает, что мы можем принимать только аудиофайлы, и атрибут capture , который указывает, что мы хотим получить его непосредственно с микрофона.

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

Этот метод работает на всех платформах. На рабочем столе пользователю будет предложено загрузить файл из файловой системы (игнорируя атрибут capture ). В Safari на iOS откроется приложение микрофона, позволяющее записать звук, а затем отправить его обратно на веб-страницу; на Android пользователю предоставляется выбор, какое приложение использовать для записи звука перед отправкой его обратно на веб-страницу.

Как только пользователь завершит запись и вернется на веб-сайт, вам нужно каким-то образом получить данные файла. Вы можете получить быстрый доступ, прикрепив событие onchange к элементу ввода, а затем прочитав свойство files объекта события.

<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>

Получив доступ к файлу, вы сможете делать с ним все, что захотите. Например, вы можете:

  • Прикрепите его непосредственно к элементу <audio> , чтобы его можно было воспроизвести.
  • Загрузите его на устройство пользователя
  • Загрузите его на сервер, прикрепив к XMLHttpRequest
  • Пропустите его через API веб-аудио и примените к нему фильтры.

Хотя использование метода элемента ввода для получения доступа к аудиоданным повсеместно распространено, это наименее привлекательный вариант. Мы очень хотим получить доступ к микрофону и обеспечить приятный опыт прямо на странице.

Доступ к микрофону в интерактивном режиме

Современные браузеры могут иметь прямую связь с микрофоном, что позволяет нам создавать возможности, полностью интегрированные с веб-страницей, и пользователь никогда не покинет браузер.

Получите доступ к микрофону

Мы можем напрямую получить доступ к микрофону, используя API в спецификации WebRTC под названием getUserMedia() . getUserMedia() предложит пользователю доступ к подключенным микрофонам и камерам.

В случае успеха API вернет Stream , который будет содержать данные либо с камеры, либо с микрофона, и затем мы сможем либо прикрепить его к элементу <audio> , либо присоединить его к потоку WebRTC, присоединить его к Web Audio AudioContext , или сохраните его с помощью API MediaRecorder .

Чтобы получить данные с микрофона, мы просто устанавливаем audio: true в объекте ограничений, который передается в 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>

Если вы хотите выбрать конкретный микрофон, вы можете сначала перечислить доступные микрофоны.

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

Затем вы можете передать deviceId , который хотите использовать, при вызове getUserMedia .

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

Само по себе это не так уж и полезно. Все, что мы можем сделать, это взять аудиоданные и воспроизвести их.

Доступ к необработанным данным с микрофона

Чтобы получить доступ к необработанным данным с микрофона, нам нужно взять поток, созданный getUserMedia() , а затем использовать API веб-аудио для обработки данных. API веб-аудио — это простой API, который принимает источники входного сигнала и подключает эти источники к узлам, которые могут обрабатывать аудиоданные (регулировать усиление и т. д.), и, в конечном итоге, к динамику, чтобы пользователь мог его слышать.

Одним из узлов, который вы можете подключить, является AudioWorkletNode . Этот узел предоставляет вам низкоуровневые возможности для пользовательской обработки звука. Фактическая обработка звука происходит в методе обратного process() в AudioWorkletProcessor . Вызовите эту функцию, чтобы передать входные данные и параметры и получить выходные данные.

Чтобы узнать больше, ознакомьтесь с Enter Audio Worklet .

<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);

Данные, хранящиеся в буферах, представляют собой необработанные данные с микрофона, и у вас есть несколько вариантов того, что вы можете делать с этими данными:

  • Загрузите его прямо на сервер
  • Храните его локально
  • Преобразуйте его в специальный формат файла, например WAV, а затем сохраните на своих серверах или локально.

Сохраните данные с микрофона

Самый простой способ сохранить данные с микрофона — использовать API MediaRecorder .

API MediaRecorder возьмет поток, созданный getUserMedia , а затем постепенно сохранит данные, находящиеся в потоке, в выбранное вами место назначения.

<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>

В нашем случае мы сохраняем данные непосредственно в массив, который позже можем превратить в Blob , который затем можно использовать для сохранения данных на нашем веб-сервере или непосредственно в хранилище на устройстве пользователя.

Спросите разрешения использовать микрофон ответственно

Если пользователь ранее не предоставил вашему сайту доступ к микрофону, то в тот момент, когда вы вызываете getUserMedia , браузер предложит пользователю предоставить вашему сайту разрешение на доступ к микрофону.

Пользователи ненавидят, когда им предлагают доступ к мощным устройствам на их компьютере, и они часто блокируют запрос или игнорируют его, если не понимают контекст, в котором был создан запрос. Лучше всего просить доступ к микрофону только тогда, когда это необходимо. После того как пользователь предоставил доступ, его больше не будут спрашивать, однако, если он отклонит доступ, вы не сможете снова запросить у пользователя разрешение.

Используйте API разрешений, чтобы проверить, есть ли у вас уже доступ

API getUserMedia не дает вам информации о том, есть ли у вас уже доступ к микрофону. Это создает проблему: чтобы обеспечить приятный пользовательский интерфейс, позволяющий пользователю предоставить вам доступ к микрофону, вам нужно запросить доступ к микрофону.

В некоторых браузерах эту проблему можно решить с помощью API разрешений. API navigator.permission позволяет вам запрашивать состояние возможности доступа к определенным API без необходимости повторного запроса.

Чтобы узнать, есть ли у вас доступ к микрофону пользователя, вы можете передать {name: 'microphone'} в метод запроса, и он вернет либо:

  • granted — пользователь ранее предоставил вам доступ к микрофону;
  • prompt — пользователь не предоставил вам доступ и будет запрошен при вызове getUserMedia ;
  • denied — система или пользователь явно заблокировали доступ к микрофону и вы не сможете получить к нему доступ.

И теперь вы можете быстро проверить, нужно ли вам изменить пользовательский интерфейс, чтобы учесть действия, которые должен выполнять пользователь.

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 () {};
});

Обратная связь