使用MediaRecorder录制5秒钟的音频片段,然后上传到服务器。

22
我想记录用户的麦克风,每个片段需要5秒,并将每个片段上传到服务器。我试过使用MediaRecorder并在5秒时间间隔调用start()和stop()方法,但当我将这些录音连接起来时会出现"丢失"声音。因此,我尝试使用start()方法的timeslice参数来记录5秒片段:
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);
  });
});

但只有第一个片段可以播放。我该怎么办,或者怎样才能使所有的“blob”都可以播放?我必须记录然后上传每个片段。我不能制作一个“blob”数组(因为用户可能会记录超过24小时的数据,而且数据需要在用户录制时上传到服务器 - 并带有5秒的延迟)。

谢谢!

2个回答

36
您需要了解媒体文件的构建方式。它不仅仅是一些原始数据可以直接转换为音频或视频。
这将取决于所选择的格式,但基本情况是您拥有所谓的元数据,这些元数据就像描述文件结构的字典。
这些元数据对于随后读取文件的软件来说是必要的,以便知道它应该如何解析包含在文件中的实际数据。
MediaRecorder API 在这里处于奇怪的位置,因为它必须能够同时编写这些元数据,并添加非确定性数据(它是一个实时记录器)。
因此,浏览器将主要的元数据放在文件的开头,以一种方式使它们能够简单地将新数据推送到文件中,并且仍然是有效的文件(即使某些信息如持续时间将缺失)。
现在,在 `datavailableEvent.data` 中获取的只是正在生成的整个文件的一部分。第一部分通常包含元数据和其他数据,具体取决于事件何时被告知触发,但下一部分不一定包含任何元数据。
因此,您不能将这些部分作为独立的文件抓取,因为生成的唯一文件是由所有这些部分组合成的单个 Blob。
那么,对于您的问题,您有不同的可能方法:
  • You could send to your server the latest slices you got from your recorder in an interval, and merge these server-side.

    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);
    

    And on your server-side, you would append the newly sent data to the being recorded file.

  • An other way would be to generate a lot of small standalone files, and to do this, you could simply generate a new MediaRecorder in an interval:

    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);
    

    Doing so, each file will be standalone, with a duration of approximately 5 seconds, and you will be able to play these files one by one.
    Now if you wish to only store a single file on server, still using this method, you can very well merge these files together on server-side too, using e.g a tool like ffmpeg.


1
非常感谢您的回答!是的,我需要每个音频片段都可以播放。我使用了第一种方法,但是这些片段无法单独播放。如果我使用第二种方法,并启动和停止录音机,则每个片段都可以播放,所以这很好,但是当我需要将它们连接起来时,每个片段之间会出现“掉落、咔嗒、包装”声音。嗯...是否有任何选项可以使用第一种方法,并将元数据(或其他内容)从第一个文件复制并粘贴到所有其他片段中?非常感谢! - MM PP
4
@MMPP 不完全是这样。第三种方式我在回答中没有提到,实际上是同时进行两个循环:一个记录短序列,另一个生成完整的记录。(只将来自完整循环的块发送到服务器) - Kaiido
我之前考虑如果我没有收到答案(因为很紧急),就创建一个悬赏。所以,由于您在我开始悬赏之前回答了问题,我想在大约23小时后向您提供50点我的声望作为奖励。谢谢! - MM PP
第三种方式确实是一个很好的选择!真的是个好主意!非常感谢你! - MM PP
只是想提一下,您可以使用 mediaRecorder.requestData() 来触发 ondataavailable 以处理新的数据块。 - Buntel
显示剩余3条评论

0

使用@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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接