Запись медиаэлемента
Хотя статья «Использование 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.
См. также
© 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