javascriptweb-audio-apiweb-mediarecorder

Getting empty blob from MediaRecorder when Web Audio API is silent


I'm recording animated canvas (WebGL) and Web Audio API output together as a video using the MediaRecorder JS class. Everything works as expected if some audio is playing, but when not, then I get empty blob when stopping MediaRecorder instance.

Below is part of my code:

const chunks = [];
var recorder = null, captureStream = null;

function dataAvailable(event) {
    chunks.push(event.data);
}

function startCapture() {
    const streamDestination = audio.context.createMediaStreamDestination();
    masterGain.connect(streamDestination);
    captureStream = canvas.captureStream();

    // This line adds audio to the recording if audio is playing
    // but if audio isn't playing at the moment, I get empty chunks
    // at the end of recording
    captureStream.addTrack(streamDestination.stream.getAudioTracks()[0]);

    recorder = new MediaRecorder(captureStream);

    recorder.ondataavailable = dataAvailable;
    recorder.onstop = exportVideo;
    recorder.start();
}

function exportVideo() {
    // Here chunks will be empty if Web Audio API
    // didn't played anything during recording
    new Blob(chunks, {'type': 'video/webm'});
    ...
}

How to get proper recording when Web Audio is silent?

Using Chrome v92 on Windows 10.


Solution

  • I believe this is https://crbug.com/1223382 that I did open a month ago, but which didn't got much traction since then.

    Basically, they still have a lot of trouble dealing with muted tracks.

    For a workaround, you could force your track to never be muted, by connecting an oscillator to a GainNode whose gain is set to 0, and connect this GainNode to your destination stream.

    const streamDestination = audio.context.createMediaStreamDestination();
    const osc = audio.context.createOscillator();
    const gainNode = audio.context.createGain();
    gainNode.gain.value = 0;
    osc.connect(gainNode);
    gainNode.connect(streamDestination);
    osc.start(0);
    // then your current code
    masterGain.connect(streamDestination);
    captureStream = canvas.captureStream();
    ...
    

    A fiddle with this snippet, fixing my repro case from the bug report.

    Caveats:


    Ps: you may also want to pass a (relatively small) time slice in MediaRecorder.start(timeslice) to workaround this other related bug, which could happen if for whatever reason one of your canvas or audio stream would get muted (e.g, if you stop drawing on your canvas for more than 5s).