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

Чтобы изучить MediaRecorder API, давайте создадим простое приложение для записи звука, используя только HTML, CSS и JavaScript.

Начиная

Для создания этого приложения нам нужен только текстовый редактор и браузер, поддерживающий MediaRecorded API. На момент написания статьи поддерживаемые браузеры включали Firefox, Chrome и Opera. Также ведется работа по внедрению этого API в Edge и Safari.

Для начала создайте папку для работы и сохраните этот файл HTML и этот файл CSS, чтобы дать нам что-то для начала. Убедитесь, что они находятся в одной папке, а файл CSS называется web-recorder-style.css. Откройте HTML-файл в браузере, вы должны увидеть следующее:

Теперь давайте взглянем на MediaRecorder API.

API медиарекордера

Чтобы начать работу с MediaRecorder API, вам понадобится MediaStream. Вы можете либо получить его из элемента <video> или <audio>, либо вызвать getUserMedia для захвата камеры и микрофона пользователя. Когда у вас есть поток, вы можете инициализировать MediaRecorder с его помощью, и вы готовы к записи.

Во время записи объект MediaRecorder будет генерировать dataavailable событий с записанными данными как часть события. Мы будем прослушивать эти события и сопоставлять фрагменты данных в массиве. Как только запись будет завершена, мы снова свяжем массив фрагментов в объект Blob. Мы можем управлять началом и концом записи, вызывая start и stop для объекта MediaRecorder.

Давайте посмотрим на это в действии.

getUserMedia

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

<script>
  window.addEventListener('DOMContentLoaded', () => {
    const getMic = document.getElementById('mic');
    const recordButton = document.getElementById('record');
    const list = document.getElementById('recordings');

  });
</script>

Далее мы проверим, поддерживает ли браузер код, который мы пишем. Если это не так, вместо этого мы отобразим ошибку на странице.

<script>
  window.addEventListener('DOMContentLoaded', () => {
    const getMic = document.getElementById('mic');
    const recordButton = document.getElementById('record');
    const list = document.getElementById('recordings');
    if ('MediaRecorder' in window) {
      // everything is good, let's go ahead
    } else {
      renderError("Sorry, your browser doesn't support the MediaRecorder API, so this demo will not work.");
    }
  });
</script>

Для метода renderError мы заменим содержимое элемента <main> сообщением об ошибке. Добавьте этот метод после прослушивателя событий.

    function renderError(message) {
      const main = document.querySelector('main');
      main.innerHTML = `<div class="error"><p>${message}</p></div>`;
    }

Если у нас есть доступ к MediaRecorder, то теперь нам нужно получить доступ к микрофону для записи. Для этого воспользуемся getUserMedia API. Мы не собираемся запрашивать доступ к микрофону сразу, так как это неудобно для любого пользователя. Вместо этого мы будем ждать, пока пользователь нажмет кнопку для доступа к микрофону, а затем спросим.

    if ('MediaRecorder' in window) {
      getMic.addEventListener('click', async () => {
        getMic.setAttribute('hidden', 'hidden');
        try {
          const stream = await navigator.mediaDevices.getUserMedia({
            audio: true,
            video: false
          });
          console.log(stream);
        } catch {
          renderError(
            'You denied access to the microphone so this demo will not work.'
          );
        }
      });
    } else {

Вызов navigator.mediaDevices.getUserMedia возвращает обещание, которое успешно разрешается, если пользователь разрешает доступ к мультимедиа. Поскольку мы используем современный JavaScript, мы можем сделать это обещание синхронным, используя async/await. Мы объявляем, что обработчик кликов является async функцией, а затем, когда дело доходит до вызова getUserMedia, мы await получаем результат, а затем продолжаем.

Пользователь может отказать в доступе к микрофону, что мы обработаем, заключив вызов в оператор try/catch. Отказ приведет к выполнению блока catch, и мы снова воспользуемся нашей функцией renderError.

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

Запись

Теперь у нас есть доступ к микрофону, мы можем подготовить наш диктофон. Мы также сохраним пару других переменных, которые нам понадобятся. Сначала тип MIME, с которым мы будем работать, «audio/webm». Похоже, это наиболее широко поддерживаемый формат, в который сегодня будут записывать браузеры. Мы также создадим массив с именем chunks, который будем использовать для хранения частей записи по мере ее создания.

MediaRecorder инициализируется медиапотоком, который мы захватили с микрофона пользователя, и объектом опций, которому мы передаем MIME-тип, который мы определили ранее. Замените console.log из предыдущего на:

        try {
          const stream = await navigator.mediaDevices.getUserMedia({
            audio: true,
            video: false
          });
          const mimeType = 'audio/webm';
          let chunks = [];
          const recorder = new MediaRecorder(stream, { type: mimeType });

Теперь, когда мы создали наш MediaRecorder, нам нужно настроить для него прослушиватели событий. Регистратор выдает события по ряду различных причин. Многие из них связаны с взаимодействием с самим рекордером, поэтому вы можете прослушивать события, когда он начинает запись, приостанавливает, возобновляет и останавливает запись. Наиболее важным событием является событие dataavailable, которое периодически генерируется, пока рекордер активно записывает. События содержат часть записи, которую мы поместим в только что созданный массив chunks.

Для нашего приложения мы собираемся прослушивать событие dataavailable, собирая фрагменты, а затем, когда срабатывает событие stop, мы собираем все фрагменты в Blob, который мы затем можем воспроизвести с элементом <audio> и сбросить массив chunks.

         const recorder = new MediaRecorder(stream, { type: mimeType });
           recorder.addEventListener('dataavailable', event => {
             if (typeof event.data === 'undefined') return;
               if (event.data.size === 0) return;
               chunks.push(event.data);
             });
           recorder.addEventListener('stop', () => {
             const recording = new Blob(chunks, {
               type: mimeType
             });
             renderRecording(recording, list);
             chunks = [];
           });

Скоро мы реализуем метод renderRecording. Нам осталось еще немного поработать, чтобы включить кнопку для запуска и остановки записи.

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

           chunks = [];
         });
         recordButton.removeAttribute('hidden');
         recordButton.addEventListener('click', () => {
           if (recorder.state === 'inactive') {
             recorder.start();
             recordButton.innerText = 'Stop';
           } else {
             recorder.stop();
             recordButton.innerText = 'Record';
           }
         });

Чтобы завершить это маленькое приложение, мы собираемся преобразовать записи в элементы <audio> и предоставить ссылку для скачивания, чтобы пользователь мог сохранить свою запись на рабочем столе. Ключевым моментом здесь является то, что мы можем взять созданный нами Blob и превратить его в URL, используя метод URL.createObjectURL. Затем этот URL можно использовать как src элемента <audio> и как href якоря. Чтобы якорь скачал файл, мы устанавливаем атрибут download.

Эта функция в основном создает элементы DOM и делает имя файла из времени, когда была сделана запись. Добавьте его под своей функцией renderError.

  function renderRecording(blob, list) {
    const blobUrl = URL.createObjectURL(blob);
    const li = document.createElement('li');
    const audio = document.createElement('audio');
    const anchor = document.createElement('a');
    anchor.setAttribute('href', blobUrl);
    const now = new Date();
    anchor.setAttribute(
      'download',
      `recording-${now.getFullYear()}-${(now.getMonth() + 1).toString().padStart(2, '0')}-${now.getDay().toString().padStart(2, '0')}--${now.getHours().toString().padStart(2, '0')}-${now.getMinutes().toString().padStart(2, '0')}-${now.getSeconds().toString().padStart(2, '0')}.webm`
    );
    anchor.innerText = 'Download';
    audio.setAttribute('src', blobUrl);
    audio.setAttribute('controls', 'controls');
    li.appendChild(audio);
    li.appendChild(anchor);
    list.appendChild(li);
  }

Тестирование

Откройте страницу в веб-браузере и нажмите кнопку Получить микрофон. Примите диалоговое окно разрешений и нажмите, чтобы начать запись. Запишите себе сообщение и воспроизведите его со страницы.

файлы WebM

Если вы загрузите одну из своих записей, вы можете обнаружить, что у вас нет медиаплеера, способного воспроизводить файл WebM. WebM — это формат с открытым исходным кодом как для аудио, так и для видео, но в основном он поддерживается браузерами. Если у вас есть VLC player, вы, вероятно, сможете воспроизвести аудио, в противном случае вы можете преобразовать его в файл MP3 или WAV с помощью онлайн-инструмента, такого как convertio (или, если вы чувствуете себя смелым, с ffmpeg в вашем Терминал).

Ваш браузер теперь является рекордером

MediaRecorder API — это мощное новое дополнение к браузерам. В этом посте мы увидели его способность записывать звук, но это еще не все. В настоящее время приложение не сохраняет аудиофайлы, поэтому при обновлении страницы они теряются. Вы можете сохранить их с помощью IndexedDB или отправить на сервер. Вы также можете поэкспериментировать с записью, представьте, что перед записью звук передается через API веб-аудио. И если формат WebM вам не по душе, вы всегда можете перекодировать звук во внешнем интерфейсе, хотя это, вероятно, работа для WebAssembly (или вашего сервера...).

Если вы хотите попробовать код из этого поста, вы можете посмотреть живую демоверсию. Весь код доступен в этом репозитории GitHub, и вы также можете ремиксовать проект на Glitch.

Дайте мне знать, что вы думаете об API MediaRecorder и для чего вы можете его использовать. Напишите в комментариях или напишите мне в Твиттере на @philnash.

Первоначально опубликовано на https://www.twilio.com.