Spec-Zone.ru › Web APIs

Запись медиаэлемента

Хотя статья «Использование API MediaStream Recording» демонстрирует использование интерфейса MediaRecorder для захвата потока медиаданных MediaStream, генерируемого аппаратным устройством, как возвращается методом navigator.mediaDevices.getUserMedia(), вы также можете использовать HTML-медиаэлемент (а именно <audio> или <video>) в качестве источника MediaStream для записи. В этой статье мы рассмотрим пример, как это сделать.

Пример записи медиаэлемента

HTML

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

<div class="left">
  <div id="startButton" class="button">Start Recording</div>
  <h2>Preview</h2>
  <video id="preview" width="160" height="120" autoplay muted></video>
</div>

Мы представляем наш основной интерфейс в двух столбцах. Слева находится кнопка «Старт» и элемент <video>, который отображает предварительный просмотр видео; это видео, которое видит камера пользователя. Обратите внимание, что используется атрибут autoplay, чтобы видео сразу отображалось при поступлении потока с камеры, и атрибут muted, чтобы звук с микрофона пользователя не воспроизводился в динамиках, предотвращая неприятную обратную связь.

<div class="right">
  <div id="stopButton" class="button">Stop Recording</div>
  <h2>Recording</h2>
  <video id="recording" width="160" height="120" controls></video>
  <a id="downloadButton" class="button">Download</a>
</div>

Справа мы видим кнопку «Стоп» и элемент <video>, который будет использоваться для воспроизведения записанного видео. Обратите внимание, что в панели воспроизведения нет атрибута autoplay (чтобы воспроизведение не начиналось сразу после получения медиаданных), и установлен атрибут controls, который указывает на отображение элементов управления для воспроизведения, паузы и т. д.

Ниже элемента воспроизведения находится кнопка для скачивания записанного видео.

Теперь давайте рассмотрим код JavaScript; именно здесь происходит основное действие!

Настройка глобальных переменных

Мы начинаем с определения некоторых глобальных переменных, которые нам понадобятся.

let preview = document.getElementById("preview");
let recording = document.getElementById("recording");
let startButton = document.getElementById("startButton");
let stopButton = document.getElementById("stopButton");
let downloadButton = document.getElementById("downloadButton");
let logElement = document.getElementById("log");

let recordingTimeMS = 5000;

Большинство из них — ссылки на элементы, с которыми мы будем работать. Последняя, recordingTimeMS, установлена в 5000 миллисекунд (5 секунд); это задаёт длительность записываемых видео.

Вспомогательные функции

Далее, мы создаём несколько вспомогательных функций, которые будут использоваться позже.

function log(msg) {
  logElement.innerText += `${msg}\n`;
}

Функция log() используется для вывода текстовых строк в элемент <div>, чтобы мы могли обмениваться информацией с пользователем. Не очень красиво, но для наших целей это выполняет задачу.

function wait(delayInMS) {
  return new Promise((resolve) => setTimeout(resolve, delayInMS));
}

Функция wait() возвращает новый Promise, который разрешается по истечении указанного количества миллисекунд. Она работает, используя стрелочную функцию, которая вызывает setTimeout(), задавая обработчик разрешения обещания в качестве обработчика таймаута. Это позволяет использовать синтаксис обещаний при использовании таймаутов, что может быть очень полезно при цепочке обещаний, как мы увидим позже.

Начало записи медиа

Функция startRecording() обрабатывает запуск процесса записи:

function startRecording(stream, lengthInMS) {
  let recorder = new MediaRecorder(stream);
  let data = [];

  recorder.ondataavailable = (event) => data.push(event.data);
  recorder.start();
  log(`${recorder.state} for ${lengthInMS / 1000} seconds…`);

  let stopped = new Promise((resolve, reject) => {
    recorder.onstop = resolve;
    recorder.onerror = (event) => reject(event.name);
  });

  let recorded = wait(lengthInMS).then(() => {
    if (recorder.state === "recording") {
      recorder.stop();
    }
  });

  return Promise.all([stopped, recorded]).then(() => data);
}

startRecording() принимает два входных параметра: поток медиаданных MediaStream для записи и длительность записи в миллисекундах. Мы всегда записываем не более указанного количества миллисекунд медиаданных, хотя если поток медиаданных прерывается до достижения этого времени, MediaRecorder также автоматически останавливает запись.

  • Сначала мы создаём MediaRecorder, который будет обрабатывать запись входного stream.
  • data — массив, изначально пустой, который хранит объекты Blob данных медиа, предоставленных обработчиком события ondataavailable.
  • Присвоение ondataavailable устанавливает обработчик события dataavailable. Полученный объект события data — это объект Blob, содержащий медиаданные. Обработчик события добавляет Blob в массив data.
  • Мы запускаем процесс записи, вызывая recorder.start(), и выводим сообщение в журнал с обновлённым состоянием рекордера и количеством секунд записи.
  • Мы создаём новое Promise с именем stopped, которое разрешается, когда обработчик события onstop рекордера вызван, и отклоняется, если вызван обработчик события onerror. Обработчик отклонения получает в качестве входного параметра имя произошедшей ошибки.
  • Мы создаём новое Promise с именем recorded, которое разрешается по истечении указанного количества миллисекунд. При разрешении оно останавливает MediaRecorder в случае, если запись активна.
  • Наконец, мы используем Promise.all для создания нового Promise , который выполняется, когда оба обещания (stopped и recorded) разрешились. После этого массив данных возвращается startRecording() своему вызывающему методу.

Остановка входного потока

Функция stop() останавливает входной поток медиаданных:

function stop(stream) {
  stream.getTracks().forEach((track) => track.stop());
}

Это делается путём вызова MediaStream.getTracks(), используя forEach() для вызова MediaStreamTrack.stop() для каждого трека в потоке.

Получение входного потока и настройка рекордера

Теперь давайте рассмотрим наиболее сложную часть кода в этом примере: обработчик событий нажатия кнопки «Старт»:

startButton.addEventListener(
  "click",
  () => {
    navigator.mediaDevices
      .getUserMedia({
        video: true,
        audio: true,
      })
      .then((stream) => {
        preview.srcObject = stream;
        downloadButton.href = stream;
        preview.captureStream =
          preview.captureStream || preview.mozCaptureStream;
        return new Promise((resolve) => (preview.onplaying = resolve));
      })
      .then(() => startRecording(preview.captureStream(), recordingTimeMS))
      .then((recordedChunks) => {
        let recordedBlob = new Blob(recordedChunks, { type: "video/webm" });
        recording.src = URL.createObjectURL(recordedBlob);
        downloadButton.href = recording.src;
        downloadButton.download = "RecordedVideo.webm";

        log(
          `Successfully recorded ${recordedBlob.size} bytes of ${recordedBlob.type} media.`,
        );
      })
      .catch((error) => {
        if (error.name === "NotFoundError") {
          log("Camera or microphone not found. Can't record.");
        } else {
          log(error);
        }
      });
  },
  false,
);

При возникновении события click происходит следующее:

  • Вызывается MediaDevices.getUserMedia для запроса нового потока медиаданных MediaStream с видео- и аудио-треками. Это поток, который мы будем записывать.

  • После разрешения обещания, возвращаемого getUserMedia(), свойство srcObject элемента предварительного просмотра <video> устанавливается в значение входного потока, что отображает видео, захваченное камерой пользователя, в окне предварительного просмотра. Поскольку элемент <video> немой, аудио не будет воспроизводиться.Ссылка кнопки «Загрузка» также устанавливается для указания потока. Далее организуется вызов preview.captureStream() функции preview.mozCaptureStream() для корректной работы кода в Firefox, где метод HTMLMediaElement.captureStream() имеет префикс. Затем создаётся и возвращается новое Promise, которое разрешается, когда начинается воспроизведение предварительного просмотра видео.

  • Когда предварительный просмотр видео начинает воспроизводиться, мы знаем, что есть медиаданные для записи, и реагируем вызовом функции startRecording(), передавая в неё поток предварительного просмотра видео (в качестве исходных медиаданных для записи) и recordingTimeMS (количество миллисекунд медиаданных для записи). Как уже упоминалось, startRecording() возвращает Promise, чья функция разрешения вызывается (получая массив объектов Blob, содержащих фрагменты записанных медиаданных), когда запись завершена.

  • Обработчик разрешения процесса записи получает массив данных медиа, локально известный как recordedChunks. Первое, что мы делаем, это объединяем фрагменты в один объект Blob с типом MIME "video/webm", используя тот факт, что конструктор Blob() конкатенирует массивы объектов в один объект. Затем используется URL.createObjectURL() для создания URL, ссылающегося на этот blob; это делается значением атрибута src элемента воспроизведения записанного видео (для воспроизведения видео из blob) и ссылкой кнопки «Скачать». Затем устанавливается атрибут download кнопки «Скачать». Хотя атрибут download может быть булевым, вы также можете задать его строковым значением, чтобы использовать его в качестве имени загружаемого файла. Таким образом, установив атрибут download ссылки на загрузку в «RecordedVideo.webm», мы сообщим браузеру, что при нажатии на кнопку должен загружаться файл с именем "RecordedVideo.webm", содержащий записанное видео.

    Затем размер и тип записанного медиа выводятся в область журнала под двумя видео и кнопкой загрузки.

  • Обработчик ошибки для всех Promise выводит ошибку в область логирования, вызывая нашу функцию log().

Обработка кнопки остановки

Последний фрагмент кода добавляет обработчик для события click на кнопке остановки, используя addEventListener():

stopButton.addEventListener(
  "click",
  () => {
    stop(preview.srcObject);
  },
  false,
);

Это вызывает функцию stop(), о которой мы говорили ранее.

Результат

В совокупности с остальной HTML-разметкой и не показанным CSS, всё выглядит и работает так:

Вы можете посмотреть полную демонстрацию здесь и использовать инструменты разработчика вашего браузера для проверки страницы и просмотра всего кода, включая скрытые части, поскольку они некритичны для понимания того, как используются API.

См. также

  • Страница Media Capture and Streams 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/Recording_a_media_element

Spec-Zone.ru

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