Record 5 seconds segments of audio using MediaRecorder and then upload to the server
Asked Answered
S

2

24

I want to record user's microphone 5 seconds long segments and upload each to the server. I tried using MediaRecorder and I called start() and stop() methods at 5 seconds time interval, but when I concatenate these recordings there is a "drop" sound between. So I tried to record 5 seconds segments using timeslice parameter of start() method:

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

But only the first segment is playable. What can I do, or how can I make all blobs playable? I MUST record then upload each segment. I CAN'T make an array of blobs (because the user could record 24hours of data or even more and the data needs to be uploaded on the server while the user is recording - with a 5 seconds delay).

Thank you!

Southeastwards answered 13/7, 2018 at 12:29 Comment(0)
I
41

You have to understand how media files are built.
It is not only some raw data that can be converted to either audio or video directly.

It will depend on the format chosen, but the basic case is that you have what is called metadata which are like a dictionary describing how the file is structured.

These metadata are necessary for the software that will then read the file to know how it should parse the actual data that is contained in the file.

The MediaRecorder API is in a strange position here, since it must be able to at the same time write these metadata, and also add non-determined data (it is a live recorder).

So what happens is that browsers will put the main metadata at the beginning of the file, in a way that they'll be able to simply push new data to the file, and still be a valid file (even though some info like duration will be missing).

Now, what you get in datavailableEvent.data is only a part of a whole file, that is being generated.
The first one will generally contain the metadata, and some other data, depending on when the event has been told to fire, but the next parts won't necessarily contain any metadata.

So you can't just grab these parts as standalone files, because the only file that is generated is the one that is made of all these parts, joined together in a single Blob.


So, to your problem, you have different possible approaches:

  • 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.

Inspiration answered 16/7, 2018 at 5:48 Comment(8)
Thank you very much for your answer! Yes, I need each audio segment to be playable. I used first method, but the segments are not playable, separately. If I use second method, and I start and stop the recorder, each segment is playable, so it is good, but when I need to join them, then there is a "drop, clack, pac" sound between each of segments. Hmmm... Is there any option to use first method, and copy and paste the metadata (or whatever is it) from the first file to all the other segments? Thank you very much!Southeastwards
@MMPP not really no. A third way I ddin't included in the answer is actually to do both simultaneously: start one loop which will record short sequences, and a bigger one that will produce the full lengthed record. (Only send the chunks from the full one to your server)Inspiration
I was thinking before to create a bounty if I don't receive an answer (because it was urgent). So, as you answered the question before I was able start the bounty, I want to offer you 50 of my reputation (in ~23 hours). Thank you!Southeastwards
Third way is really a good option! Really good idea! Thank you so much!Southeastwards
@MMPP You might want to look at #52818466. Although the solution given here is deprecated but there is a new AudioWorklet API which can be used.Renatorenaud
@MMPP may i know how did u resolved this problem? i am currently facing the same problem... can you please provide solution ?Dispel
@AkbarBasha I didn't solve this problem using these methods. You can try using WebRTC. Personally I created some computer software in C, using FFMPEG in order to solve it fast, so without web browser.Southeastwards
just want to mention you can although use mediaRecorder.requestData() to trigger ondataavailable to process a new chunk of data.Dela
C
0

Using a version of one of the @Kalido's suggestions I got this working. It will send small standalone files to the server that it won't produce any glitch on image or sound when they are concatenated as an unified file on the server side:

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
}

Latter on the server I concatenate them with this command:

# 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
Carbamate answered 1/12, 2021 at 15:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.