Search code examples
javascriptaudiomediastreamwavesurfer.js

Audio won't play anymore on browser after recording it with MediaRecorder


(See https://github.com/norbjd/wavesurfer-upload-and-record for a minimal reproducible example).

I'm using wavesurfer.js to display audio uploaded by the user as a waveform, and I'm trying to add a feature for recording a part of the audio uploaded.

So I've created a "Record" button (for now recording only 5 seconds of the audio) with the following code when clicking on it. I'm using MediaRecorder API :

document
    .querySelector('[data-action="record"]')
    .addEventListener('click', () => {
        // re-use audio context from wavesurfer instead of creating a new one
        const audioCtx = wavesurfer.backend.getAudioContext();
        const dest = audioCtx.createMediaStreamDestination();
        const audioStream = dest.stream;

        audioCtx.createMediaElementSource(audio).connect(dest);

        const chunks = [];
        const rec = new MediaRecorder(audioStream);
        rec.ondataavailable = (e) => {
            chunks.push(e.data);
        }
        rec.onstop = () => {
            const blob = new Blob(chunks, { type: "audio/ogg" });
            const a = document.createElement("a");
            a.download = "export.ogg";
            a.href = URL.createObjectURL(blob);
            a.textContent = "export the audio";
            a.click();
            window.URL.revokeObjectURL(a.href);
        }

        wavesurfer.play();
        rec.start();

        setTimeout(() => {
            rec.stop();
            wavesurfer.stop();
        }, 5 * 1000);
    });

When clicking on the button for recording, the wavesurfer should play (wavesurfer.play()) but I can't hear anything from my browser (but I can see the cursor move). At the end of the recording (5 seconds, set with setTimeout), I can download the recorded audio (rec.onstop function) and the sound plays correctly in VLC or any other media player.

However, I can't play audio anymore on the webpage via the browser. I can still record audio, and recorded audio can be downloaded and played correctly.

I'm wondering why audio won't play on the browser after clicking on the "Record" button for the first time. I think that this line :

audioCtx.createMediaElementSource(audio).connect(dest);

is the issue, but without it, I can't record audio.

I've also tried to recreate a new AudioContext instead of using wavesurfer's one :

const audioCtx = new AudioContext();

but it does not work better (same issue).

I've reproduced the issue in a minimal reproducible example : https://github.com/norbjd/wavesurfer-upload-and-record, so feel free to check it. Any help will be welcomed !


Solution

  • You don't need a separate audiocontext, but you need a MediaStreamDestination that you create using the same audiocontext (from wavesurfer.js in your case) as for the audionode you want to record, and you need to connect the audionode to that destination.

    You can see a complete example of capturing audio and screen video here:

    https://github.com/petersalomonsen/javascriptmusic/blob/master/wasmaudioworklet/screenrecorder/screenrecorder.js

    ( connecting the audionode to record is done after the recording has started on line 52 )

    and you can test it live here: https://petersalomonsen.com/webassemblymusic/livecodev2/?gist=c3ad6c376c23677caa41eb79dddb5485

    (Toggle the capture checkbox to start recording and press the play button to start the music, toggle the capture checkbox again to stop the recording).

    and you can see the actual recording being done on this video: https://youtu.be/FHST7rLxhLM

    as you can see in that example, it is still possible to play audio after the recording is finished.

    Note that this example has only been tested for Chrome and Firefox.

    And specifically for your case with wavesurfer:

    Instead of just backend: 'MediaElement', switch to backend: 'MediaElementWebAudio',

    and instead of audioCtx.createMediaElementSource(audio).connect(dest);, you can change to wavesurfer.backend.sourceMediaElement.connect(dest); to reuse the existing source from wavesurfer (but also works without this).