Spec-Zone.ru › Web APIs

Использование API MediaStream Recording

API MediaStream Recording упрощает запись потоков аудио и/или видео. При использовании с navigator.mediaDevices.getUserMedia() он предоставляет простой способ записи с устройств ввода пользователя и мгновенного использования результата в веб-приложениях.

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

Пример приложения: Веб-диктофон

An image of the Web dictaphone sample app - a sine wave sound visualization, then record and stop buttons, then an audio jukebox of recorded tracks that can be played back.

Для демонстрации базового использования API MediaStream Recording мы создали веб-диктофон. Он позволяет записывать фрагменты аудио и затем воспроизводить их. Он даже предоставляет визуализацию входного звука устройства, используя Web Audio API. В этой статье мы сосредоточимся на функциональности записи и воспроизведения.

Вы можете посмотреть эту демонстрацию в реальном времени или скачать исходный код на GitHub.

Полезные элементы CSS

HTML в этом приложении довольно прост, поэтому мы не будем его подробно рассматривать; однако есть несколько более интересных элементов CSS, которые стоит упомянуть, поэтому мы обсудим их ниже. Если вы не заинтересованы в CSS и хотите сразу перейти к JavaScript, перейдите к разделу Настройка базового приложения.

Ограничение интерфейса размерами viewport независимо от высоты устройства с помощью calc()

Функция calc() — это одна из полезных небольших функций, появившаяся в CSS, которая поначалу не выглядит значимой, но быстро заставляет задуматься: "Почему раньше этого не было? Почему макет CSS2 был таким неудобным?" Она позволяет выполнять вычисления для определения вычисленного значения единицы CSS, смешивая различные единицы в процессе.

Например, в Web Dictaphone у нас есть три основных области пользовательского интерфейса, расположенные вертикально. Мы хотели задать фиксированные высоты для первых двух (заголовка и элементов управления):

header {
  height: 70px;
}

.main-controls {
  padding-bottom: 0.7rem;
  height: 170px;
}

Однако мы хотели, чтобы третья область (содержащая записанные фрагменты, которые можно воспроизводить) занимала всё оставшееся пространство, независимо от высоты устройства. Flexbox может быть здесь решением, но это немного избыточно для такого простого макета. Вместо этого проблема была решена путем задания высоты третьего контейнера равной 100% высоты родительского контейнера, минус высоты и отступы других двух:

.sound-clips {
  box-shadow: inset 0 3px 4px rgb(0 0 0 / 70%);
  background-color: rgb(0 0 0 / 10%);
  height: calc(100% - 240px - 0.7rem);
  overflow: scroll;
}

Хак с чекбоксами для отображения/скрытия

Это уже довольно хорошо документировано, но мы хотели упомянуть хак с чекбоксами, который использует тот факт, что вы можете нажать на <label> чекбокса, чтобы переключить его в состояние "выбран/не выбран". В Web Dictaphone это позволяет управлять экраном информации, который отображается/скрывается при нажатии на значок вопроса в верхнем правом углу. Прежде всего, мы стилизуем <label> так, как хотим, убедившись, что у него достаточный z-index, чтобы он всегда находился над другими элементами и, следовательно, был фокусируемым/нажимаемым:

label {
  font-family: "NotoColorEmoji";
  font-size: 3rem;
  position: absolute;
  top: 2px;
  right: 3px;
  z-index: 5;
  cursor: pointer;
}

Затем мы скрываем сам чекбокс, потому что не хотим, чтобы он засорял наш пользовательский интерфейс:

input[type="checkbox"] {
  position: absolute;
  top: -100px;
}

Далее, мы стилизуем экран информации (вложенный в элемент <aside>) так, как хотим, задаём ему фиксированное позиционирование, чтобы он не отображался в макете и не влиял на основной интерфейс, трансформируем его в позицию, в которой он должен находиться по умолчанию, и даём ему плавную анимацию отображения/скрытия:

aside {
  position: fixed;
  top: 0;
  left: 0;
  text-shadow: 1px 1px 1px black;
  width: 100%;
  height: 100%;
  transform: translateX(100%);
  transition: 0.6s all;
  background-color: #999;
  background-image: linear-gradient(
    to top right,
    rgb(0 0 0 / 0%),
    rgb(0 0 0 / 50%)
  );
}

Наконец, мы создаём правило, указывающее, что при выборе чекбокса (при нажатии/фокусировании на метке) смещение по горизонтали смежного элемента <aside> будет изменено, и он плавно переместится в видимую область:

input[type="checkbox"]:checked ~ aside {
  transform: translateX(0);
}

Настройка базового приложения

Для получения потока медиа, который мы хотим получить, мы используем getUserMedia(). Затем мы используем API MediaStream Recording для записи потока и вывода каждого записанного фрагмента в источник сгенерированного элемента <audio>, чтобы его можно было воспроизвести.

Мы объявим некоторые переменные для кнопок записи и остановки, а также для <article>, который будет содержать сгенерированные аудиоплееры:

const record = document.querySelector(".record");
const stop = document.querySelector(".stop");
const soundClips = document.querySelector(".sound-clips");

Наконец, для этого раздела мы настроим базовую getUserMedia структуру:

if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
  console.log("getUserMedia supported.");
  navigator.mediaDevices
    .getUserMedia(
      // constraints - only audio needed for this app
      {
        audio: true,
      },
    )

    // Success callback
    .then((stream) => {})

    // Error callback
    .catch((err) => {
      console.error(`The following getUserMedia error occurred: ${err}`);
    });
} else {
  console.log("getUserMedia not supported on your browser!");
}

Всё это заключено в проверку, которая проверяет, поддерживается ли getUserMedia, прежде чем выполнять что-либо ещё. Затем мы вызываем getUserMedia() и внутри него определяем:

  • Ограничения: Для нашего диктофона должен быть захвачен только аудио.
  • Обработчик успеха: Этот код выполняется после успешного завершения вызова getUserMedia.
  • Обработчик ошибок/неудачи: Этот код выполняется, если вызов getUserMedia завершается неудачно по любой причине.

Примечание: Весь код ниже размещается внутри getUserMedia обработчика успеха.

Захват потока медиа

После того, как getUserMedia успешно создал поток медиа, вы создаёте новый экземпляр MediaRecorder с помощью конструктора MediaRecorder() и передаёте ему поток напрямую. Это ваша точка входа в использование API MediaStream Recording — поток теперь готов быть преобразован в Blob в формате кодирования по умолчанию вашего браузера.

const mediaRecorder = new MediaRecorder(stream);

В интерфейсе MediaRecorder есть ряд методов, которые позволяют управлять записью потока медиа; в Web Dictaphone мы используем только два и реагируем на некоторые события. Прежде всего, MediaRecorder.start() используется для запуска записи потока при нажатии кнопки "Запись":

record.onclick = () => {
  mediaRecorder.start();
  console.log(mediaRecorder.state);
  console.log("recorder started");
  record.style.background = "red";
  record.style.color = "black";
};

Пока MediaRecorder записывает, свойство MediaRecorder.state вернёт значение "recording".

По мере записи нам нужно собирать аудиоданные. Мы регистрируем обработчик события, используя ondataavailable:

let chunks = [];

mediaRecorder.ondataavailable = (e) => {
  chunks.push(e.data);
};

Примечание: Браузер будет генерировать события dataavailable по мере необходимости, но если вы хотите вмешаться, вы также можете включить интервал при вызове метода start() — например, start(10000) — для управления этим интервалом или вызвать MediaRecorder.requestData() для запуска события, когда вам это нужно.

Наконец, мы используем метод MediaRecorder.stop() для остановки записи при нажатии на кнопку "Стоп" и окончательно формируем Blob, готовый к использованию в другом месте нашего приложения.

stop.onclick = () => {
  mediaRecorder.stop();
  console.log(mediaRecorder.state);
  console.log("recorder stopped");
  record.style.background = "";
  record.style.color = "";
};

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

Получение и использование blob

Когда запись завершится, свойство state принимает значение "inactive", и генерируется событие stop. Мы регистрируем обработчик события для этого, используя onstop, и завершаем формирование нашего blob из всех полученных фрагментов:

mediaRecorder.onstop = (e) => {
  console.log("recorder stopped");

  const clipName = prompt("Enter a name for your sound clip");

  const clipContainer = document.createElement("article");
  const clipLabel = document.createElement("p");
  const audio = document.createElement("audio");
  const deleteButton = document.createElement("button");

  clipContainer.classList.add("clip");
  audio.setAttribute("controls", "");
  deleteButton.textContent = "Delete";
  clipLabel.textContent = clipName;

  clipContainer.appendChild(audio);
  clipContainer.appendChild(clipLabel);
  clipContainer.appendChild(deleteButton);
  soundClips.appendChild(clipContainer);

  const blob = new Blob(chunks, { type: "audio/ogg; codecs=opus" });
  chunks = [];
  const audioURL = window.URL.createObjectURL(blob);
  audio.src = audioURL;

  deleteButton.onclick = (e) => {
    let evtTgt = e.target;
    evtTgt.parentNode.parentNode.removeChild(evtTgt.parentNode);
  };
};

Давайте рассмотрим приведенный выше код и посмотрим, что происходит.

Сначала мы отображаем запрос, предлагающий пользователю назвать запись.

Затем мы создаём HTML-структуру, подобную следующей, вставляя её в контейнер записи, который является элементом <article>.

<article class="clip">
  <audio controls></audio>
  <p>your clip name</p>
  <button>Delete</button>
</article>

После этого мы создаём объединённый Blob из фрагментов записанного аудио и создаём URL-адрес объекта, указывающий на него, используя window.URL.createObjectURL(blob). Затем мы устанавливаем значение атрибута <audio> элемента src на URL объекта, чтобы при нажатии на кнопку воспроизведения в аудиоплеере воспроизводился Blob.

Наконец, мы устанавливаем обработчик onclick на кнопку удаления, который будет функцией, удаляющей всю структуру HTML записи.

Спецификации

Спецификация
Запись MediaStream
# mediarecorder-api

Совместимость с браузерами

Рабочие столы Мобильные устройства
Chrome Edge Firefox Opera Safari Chrome Android Firefox для Android Opera Android Safari на iOS Samsung Internet WebView Android
MediaRecorder 47 79 25 36 14.1 47 25 36 14 5.0 47
Using_the_MediaStream_Recording_API 47 79
25До Firefox 58, использование MediaStream.addTrack() на потоке, полученном с помощью getUserMedia(), а затем попытка записи результирующего потока приводили к записи только исходного потока без добавленных треков (критическая ошибка).
36 14.1 47
25До Firefox for Android 58, использование MediaStream.addTrack() на потоке, полученном с помощью getUserMedia(), а затем попытка записи результирующего потока приводили к записи только исходного потока без добавленных треков (критическая ошибка).
36 14 5.0 47
audioBitrateMode 89 89 Нет 75 Нет 89 Нет 63 Нет 15.0 89
audioBitsPerSecond 49 79 71 36 14.1 49 79 36 14.5 5.0 49
dataavailable_event 49 79 25 36 14.1 49 25 36 14 5.0 49
error_event
49Интерфейс для этого события представляет собой обычный Event, а не ErrorEvent. См. ошибку 40791876.
79Интерфейс для этого события представляет собой обычный Event, а не ErrorEvent. См. ошибку 40791876.
25
36Интерфейс для этого события представляет собой обычный Event, а не ErrorEvent. См. ошибку 40791876.
14.1
49Интерфейс для этого события представляет собой обычный Event, а не ErrorEvent. См. ошибку 40791876.
25
36Интерфейс для этого события представляет собой обычный Event, а не ErrorEvent. См. ошибку 40791876.
14
5.0Интерфейс для этого события представляет собой обычный Event, а не ErrorEvent. См. ошибку 40791876.
49Интерфейс для этого события представляет собой обычный Event, а не ErrorEvent. См. ошибку 40791876.
isTypeSupported_static 47 79 25 36 14.1 47 25 36 14 5.0 47
mimeType 49
47–49До Chrome 49 поддерживалось только видео, а не аудио.
79
25Начиная с Firefox 71, поведение mimeType стало более согласованным. Например, теперь он возвращает тип носителя даже после остановки записи.
36 14.1 49
47–49До Chrome Android 49 поддерживалось только видео, а не аудио.
25 36 14 5.0 49
47–49До WebView Android 49 поддерживалось только видео, а не аудио.
pause 49 79 25 36 14.1 49 25 36 14.5 5.0 49
pause_event 49 79 65 36 14.1 49 65 36 14.5 5.0 49
requestData 49 79 25 36 14.1 49 25 36 14 5.0 49
resume 49 79 25 36 14.1 49 25 36 14.5 5.0 49
resume_event 49 79 65 36 14.1 49 65 36 14.5 5.0 49
start 47 79 25 36 14.1 47 25 36 14 5.0 47
start_event 49 79 25 36 14.1 49 25 36 14 5.0 49
state 49
47–49До Chrome 49 поддерживалось только видео, а не аудио.
79 25 36 14.1 49
47–49До Chrome Android 49 поддерживалось только видео, а не аудио.
25 36 14 5.0 49
47–49До WebView Android 49 поддерживалось только видео, а не аудио.
stop 49 79 25 36 14.1 49 25 36 14 5.0 49
stop_event 49 79 25 36 14.1 49 25 36 14 5.0 49
stream 49
47–49До Chrome 49 поддерживается только видео, а не аудио.
79 25 36 14.1 49
47–49До Chrome Android 49 поддерживается только видео, а не аудио.
25 36 14 5.0 49
videoBitsPerSecond 49 79 71 36 14.1 49 79 36 14.5 5.0 49

См. также

  • Страница API захвата медиа и потоков
  • MediaDevices.getUserMedia()

© 2005–2024 MDN contributors.
Licensed under the Creative Commons Attribution-ShareAlike License v2.5 or later.
https://developer.mozilla.org/en-US/docs/Web/API/MediaStream_Recording_API/Using_the_MediaStream_Recording_API

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API