javascriptaudiochunksrecorderweb-mediarecorder

JavaScript : How to split an audio blob into 1 second chunks and export to wav files using recorder.js?


I want to record voice, split the recorded voice (or the audio blob) automatically into 1 second chunks, export each chunk to a wav file and send to the back end . This should happen asynchronously while the user speaks.

I currently use the following recorder.js library to do the above tasks https://cdn.rawgit.com/mattdiamond/Recorderjs/08e7abd9/dist/recorder.js

My problem is, with time the blob/wave file becomes bigger in size. I think it is because the data gets accumulated and make the chunk size bigger. So with time I am not actually sending sequential 1 second chunks but accumulated chunks.

I can’t figure our where in my code this issue is caused. May be this happens inside the recorder.js library. If someone has used recorder js or any other JavaScript method for a similar tasks, appreciate if you could go through this code and let me know where it breaks.

This is my JS code

var gumStream; // Stream from getUserMedia()
var rec; // Recorder.js object
var input; // MediaStreamAudioSourceNode we'll be recording
var recordingNotStopped; // User pressed record button and keep talking, still not stop button pressed
const trackLengthInMS = 1000; // Length of audio chunk in miliseconds
const maxNumOfSecs = 1000; // Number of mili seconds we support per recording (1 second)


// Shim for AudioContext when it's not available. 
var AudioContext = window.AudioContext || window.webkitAudioContext;
var audioContext //audio context to help us record

var recordButton = document.getElementById("recordButton");
var stopButton = document.getElementById("stopButton");

//Event handlers for above 2 buttons
recordButton.addEventListener("click", startRecording);
stopButton.addEventListener("click", stopRecording);

//Asynchronous function to stop the recoding in each second and export blob to a wav file
const sleep = time => new Promise(resolve => setTimeout(resolve, time));

const asyncFn = async() => {
  for (let i = 0; i < maxNumOfSecs; i++) {
    if (recordingNotStopped) {
      rec.record();
      await sleep(trackLengthInMS);
      rec.stop();

      //stop microphone access
      gumStream.getAudioTracks()[0].stop();

      //Create the wav blob and pass it on to createWaveBlob
      rec.exportWAV(createWaveBlob);
    }
  }
}

function startRecording() {
  console.log("recordButton clicked");
  recordingNotStopped = true;
  var constraints = {
    audio: true,
    video: false
  }

  recordButton.disabled = true;
  stopButton.disabled = false;

  //Using the standard promise based getUserMedia() 
  navigator.mediaDevices.getUserMedia(constraints).then(function(stream) {

    //Create an audio context after getUserMedia is called
    audioContext = new AudioContext();

    // Assign to gumStream for later use 
    gumStream = stream;

    //Use the stream 
    input = audioContext.createMediaStreamSource(stream);

    //Create the Recorder object and configure to record mono sound (1 channel)
    rec = new Recorder(input, {
      numChannels: 1
    });

    //Call the asynchronous function to split and export audio
    asyncFn();
    console.log("Recording started");

  }).catch(function(err) {
    //Enable the record button if getUserMedia() fails
    recordButton.disabled = false;
    stopButton.disabled = true;
  });
}

function stopRecording() {
  console.log("stopButton clicked");
  recordingNotStopped = false;

  //disable the stop button and enable the record button to  allow for new recordings
  stopButton.disabled = true;
  recordButton.disabled = false;

  //Set the recorder to stop the recording
  rec.stop();

  //stop microphone access
  gumStream.getAudioTracks()[0].stop();
}

function createWaveBlob(blob) {
  var url = URL.createObjectURL(blob);

  //Convert the blob to a wav file and call the sendBlob function to send the wav file to the server
  var convertedfile = new File([blob], 'filename.wav');
  sendBlob(convertedfile);
}


Solution

  • Recorder.js keeps a record buffer of the audio that it records. When exportWAV is called, the record buffer is encoded but not cleared. You'd need to call clear on the recorder before calling record again so that the previous chunk of audio is cleared from the record buffer.

    This is how it was fixed in the above code.

    //Extend the Recorder Class and add clear() method
    Recorder.prototype.step = function () {
        this.clear();
    };
    
    //After calling the exportWAV(), call the clear() method
    rec.exportWAV(createWaveBlob);
    rec.step();