Запишите 5-секундные сегменты аудио с помощью MediaRecorder и затем загрузите на сервер
Я хочу записать микрофон пользователя длиной 5 секунд и загрузить каждый на сервер. Я пытался использовать MediaRecorder, и я вызывал методы start() и stop() с интервалом в 5 секунд, но когда я объединяю эти записи, между ними появляется звук "сбрасывания". Поэтому я попытался записать 5-секундные сегменты, используя параметр timeslice метода start():
navigator.mediaDevices.getUserMedia({ audio: { channelCount: 2, volume: 1.0, echoCancellation: false, noiseSuppression: false } }).then(function(stream) {
const Recorder = new MediaRecorder(stream, { audioBitsPerSecond: 128000, mimeType: "audio/ogg; codecs=opus" });
Recorder.start(5000);
Recorder.addEventListener("dataavailable", function(event) {
const audioBlob = new Blob([event.data], { type: 'audio/ogg' });
upload(audioBlob);
});
});
Но только первый сегмент является играбельным. Что я могу сделать или как сделать все сгустки играбельными? Я ДОЛЖЕН записать, а затем загрузить каждый сегмент. Я НЕ МОГУ создавать массив больших двоичных объектов (потому что пользователь может записывать данные за 24 часа или даже больше, а данные должны быть загружены на сервер во время записи - с задержкой в 5 секунд).
Спасибо!
2 ответа
Вы должны понимать, как создаются медиа-файлы.
Это не только некоторые необработанные данные, которые могут быть преобразованы в аудио или видео напрямую.
Это будет зависеть от выбранного формата, но основной случай заключается в том, что у вас есть то, что называется метаданными, которые похожи на словарь, описывающий структуру файла.
Эти метаданные необходимы программному обеспечению, которое затем будет читать файл, чтобы знать, как оно должно анализировать фактические данные, содержащиеся в файле.
API MediaRecorder находится здесь в странном положении, поскольку он должен быть способен одновременно записывать эти метаданные, а также добавлять неопределенные данные (это живой рекордер).
Так что получается, что браузеры будут помещать основные метаданные в начало файла таким образом, что они смогут просто помещать новые данные в файл и при этом оставаться действительным файлом (даже если некоторая информация, такая как продолжительность, будет отсутствовать).
Теперь, что вы получаете в datavailableEvent.data
это только часть целого файла, который генерируется.
Первая, как правило, будет содержать метаданные и некоторые другие данные, в зависимости от того, когда было указано событие, но следующие части не обязательно будут содержать какие-либо метаданные.
Таким образом, вы не можете просто взять эти части как отдельные файлы, потому что генерируется единственный файл, который состоит из всех этих частей, объединенных в один большой двоичный объект.
Итак, к вашей проблеме у вас есть разные возможные подходы:
Вы можете отправить на сервер самые последние срезы, которые вы получили с вашего рекордера, и объединить их на стороне сервера.
const recorder = new MediaRecorder(stream); const chunks = []; recorder.ondataavailable = e => chunks.push(e.data); recorder.start(); // you don't need the timeslice argument setInterval(()=>{ // here we both empty the 'chunks' array, and send its content to the server sendToServer(new Blob(chunks.splice(0,chunks.length))) }, 5000);
А на стороне сервера вы добавляете вновь отправленные данные в записываемый файл.
Другим способом было бы создать много небольших автономных файлов, и для этого вы могли бы просто сгенерировать новый MediaRecorder за интервал:
function record_and_send(stream) { const recorder = new MediaRecorder(stream); const chunks = []; recorder.ondataavailable = e => chunks.push(e.data); recorder.onstop = e => sendToServer(new Blob(chunks)); setTimeout(()=> recorder.stop(), 5000); // we'll have a 5s media file recorder.start(); } // generate a new file every 5s setInterval(record_and_send, 5000);
При этом каждый файл будет автономным, длительностью около 5 секунд, и вы сможете воспроизводить эти файлы один за другим.
Теперь, если вы хотите сохранить только один файл на сервере, все еще используя этот метод, вы можете очень хорошо объединить эти файлы вместе на стороне сервера, используя, например, такой инструмент, как ffmpeg.
Используя версию одного из предложений @Kalido, я заработал. Он будет отправлять небольшие автономные файлы на сервер, чтобы не возникало никаких сбоев в изображении или звуке, когда они объединяются в единый файл на стороне сервера:
var mediaRecorder;
var recordingRunning = false;
var chunks;
// call this function to start the process
function startRecording(stream) {
recordingRunning = true;
chunks = [];
mediaRecorder = new MediaRecorder(stream, { mimeType: "video/webm; codecs=vp9" });
mediaRecorder.ondataavailable = function (e) {
chunks.push(e.data);
};
mediaRecorder.onstop = function () {
actualChunks = chunks.splice(0, chunks.length);
const blob = new Blob(actualChunks, { type: "video/webm; codecs=vp9" });
uploadVideoPart(blob); // Upload to server
};
recordVideoChunk(stream);
};
// call this function to stop the process
function stopRecording(stream) {
recordingRunning = false
mediaRecorder.stop();
};
function recordVideoChunk(stream) {
mediaRecorder.start();
setTimeout(function() {
if(mediaRecorder.state == "recording")
mediaRecorder.stop();
if(recordingRunning)
recordVideoChunk(stream);
}, 10000); // 10 seconds videos
}
Позже на сервере я объединяю их с помощью этой команды:
# list.txt
file 'chunk1'
file 'chunk2'
file 'chunk3'
# command
ffmpeg -avoid_negative_ts 1 -f concat -safe 0 -i list.txt -c copy output.mp4