'How to record audio played in browser using JS (WebAPI)

I'm posting this because I couldn't find an answer that works.

So what I'm trying to do is to let the visitor to the site play a sound by clicking on an element and let them record this. (Please refer to the code below)

const audio1 = new Audio('https://sun.sw3web.com/wp-content/uploads/sw3/assets/audio/beep1.mp3');
const audio2 = new Audio('https://sun.sw3web.com/wp-content/uploads/sw3/assets/audio/beep2.mp3');
const audio3 = new Audio('https://sun.sw3web.com/wp-content/uploads/sw3/assets/audio/beep3.mp3');
const audio4 = new Audio('https://sun.sw3web.com/wp-content/uploads/sw3/assets/audio/beep4.mp3');

$('#btn1').click( () => audio1.play() );    
$('#btn2').click( () => audio2.play() );    
$('#btn3').click( () => audio3.play() );    
$('#btn4').click( () => audio4.play() );

So I need a way to record the audio that originating from the browser to the speakers.

I cannot use record the audio from microphone since one might use a handsfree and thus there won't be any sound to record.

The problem i'm facing is how to get a stream object that I can attach to a MediaRecorder object.

I tried using MediaRecorder class but couldn't find a way to allocated a Stream from the standard audio output device. This is my code so far. It only records the input from the microphone.

//Play module

jQuery(document).ready(function($){

const audio1 = new Audio('https://sun.sw3web.com/wp-content/uploads/sw3/assets/audio/beep1.mp3');
const audio2 = new Audio('https://sun.sw3web.com/wp-content/uploads/sw3/assets/audio/beep2.mp3');
const audio3 = new Audio('https://sun.sw3web.com/wp-content/uploads/sw3/assets/audio/beep3.mp3');
const audio4 = new Audio('https://sun.sw3web.com/wp-content/uploads/sw3/assets/audio/beep4.mp3');

$('#btn1').click( () => audio1.play() );    
$('#btn2').click( () => audio2.play() );    
$('#btn3').click( () => audio3.play() );    
$('#btn4').click( () => audio4.play() );



//recording module
//-Steps
//-- 1) Start recording the audio
//-- 2) While recording, store the audio data chunks
//-- 3) Stop recording the audio
//-- 4) Convert the audio data chunks to a single audio data blob
//-- 5) Create a URL for that single audio data blob
//-- 6) Play the audio


//API to handle audio recording

var audioRecorder = {
    
    /** Stores the recorded audio as Blob objects of audio data as the recording continues*/
    audioBlobs: [], /*of type Blob[]*/
    
    /** Stores the reference of the MediaRecorder instance that handles the MediaStream when recording starts*/
    mediaRecorder: null, /*of type MediaRecorder*/
    
    /** Stores the reference to the stream currently capturing the audio*/
    streamBeingCaptured: null, /*of type MediaStream*/
    
    /** Start recording the audio
      * @returns {Promise} - returns a promise that resolves if audio recording successfully started
      */
      
    start: function () {
        
        //Feature Detection
        
        if (!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia)) {
            
            //Feature is not supported in browser
            //return a custom error
            
            return Promise.reject(new Error('mediaDevices API or getUserMedia method is not supported in this browser.'));
            
        }
        else {
            
            //Feature is supported in browser
            //create an audio stream
            
            return navigator.mediaDevices.getUserMedia({ audio: true }/*of type MediaStreamConstraints*/)
                //returns a promise that resolves to the audio stream
                
                .then(stream /*of type MediaStream*/ => {
                     
                    //save the reference of the stream to be able to stop it when necessary
                     audioRecorder.streamBeingCaptured = stream;

                    //create a media recorder instance by passing that stream into the MediaRecorder constructor
                    audioRecorder.mediaRecorder = new MediaRecorder(stream); /*the MediaRecorder interface of the MediaStream Recording
                    API provides functionality to easily record media*/

                    //clear previously saved audio Blobs, if any
                    audioRecorder.audioBlobs = [];

                    //add a dataavailable event listener in order to store the audio data Blobs when recording
                    audioRecorder.mediaRecorder.addEventListener("dataavailable", event => {
                        //store audio Blob object
                        audioRecorder.audioBlobs.push(event.data);
                    });

                    //start the recording by calling the start method on the media recorder
                    audioRecorder.mediaRecorder.start();
            });
        }
    },
    
    /** Stop the started audio recording
      * @returns {Promise} - returns a promise that resolves to the audio as a blob file
      */
      
    stop: function () {
        
        //return a promise that would return the blob or URL of the recording
        return new Promise(resolve => {
            
            //save audio type to pass to set the Blob type
            let mimeType = 'audio/webm';
            
            if(MediaRecorder.isTypeSupported(mimeType)) {
                
                //listen to the stop event in order to create & return a single Blob object
                audioRecorder.mediaRecorder.addEventListener("stop", () => {
                    //create a single blob object, as we might have gathered a few Blob objects that needs to be joined as one
                    let audioBlob = new Blob(audioRecorder.audioBlobs, { type: mimeType });
     
                    //resolve promise with the single audio blob representing the recorded audio
                    resolve(audioBlob);
                });
     
                //stop the recording feature
                audioRecorder.mediaRecorder.stop();
         
                //stop all the tracks on the active stream in order to stop the stream
                audioRecorder.stopStream();
         
                //reset API properties for next recording
                audioRecorder.resetRecordingProperties();
                
            }
            else {
                //mime type is not supported
                this.cancel();
                Promise.reject(new Error('Mimetype '+mimeType+' is not supported in this browser'));
               
            }
            
        });
        
    },
    
    /** Stop all the tracks on the active stream in order to stop the stream and remove
 * the red flashing dot showing in the tab
     */
    stopStream: function() {
        //stopping the capturing request by stopping all the tracks on the active stream
        audioRecorder.streamBeingCaptured.getTracks() //get all tracks from the stream
                .forEach(track /*of type MediaStreamTrack*/ => track.stop()); //stop each one
    },
    
    /** Reset all the recording properties including the media recorder and stream being captured*/
    resetRecordingProperties: function () {
        audioRecorder.mediaRecorder = null;
        audioRecorder.streamBeingCaptured = null;
 
        /*No need to remove event listeners attached to mediaRecorder as
        If a DOM element which is removed is reference-free (no references pointing to it), the element itself is picked
        up by the garbage collector as well as any event handlers/listeners associated with it.
        getEventListeners(audioRecorder.mediaRecorder) will return an empty array of events.*/
    },
    
    /** Cancel audio recording*/
    
    cancel: function () {
        
        //stop the recording feature
        audioRecorder.mediaRecorder.stop();
 
        //stop all the tracks on the active stream in order to stop the stream
        audioRecorder.stopStream();
 
        //reset API properties for next recording
        audioRecorder.resetRecordingProperties();
        
    }  
    
}// End of AudioRecorder object


/*----------------------------*/
/*      Controller code       */
/*----------------------------*/

/** Starts the audio recording*/
 
function startAudioRecording() {
    
    //start recording using the audio recording API
    
    audioRecorder.start()
    
        .then(() => { //on success
            console.log("Recording Audio...")    ;
        })    
        .catch(error => { //on error
            //No Browser Support Error
            if (error.message.includes("mediaDevices API or getUserMedia method is not supported in this browser.")) {       
                console.log("To record audio, use browsers like Chrome and Firefox.");
            }
            //Error handling structure
            switch (error.name) {
                case 'AbortError': //error from navigator.mediaDevices.getUserMedia
                    console.log("An AbortError has occured.");
                    break;
                case 'NotAllowedError': //error from navigator.mediaDevices.getUserMedia
                    console.log("A NotAllowedError has occured. User might have denied permission.");
                    break;
                case 'NotFoundError': //error from navigator.mediaDevices.getUserMedia
                    console.log("A NotFoundError has occured.");
                    break;
                case 'NotReadableError': //error from navigator.mediaDevices.getUserMedia
                    console.log("A NotReadableError has occured.");
                    break;
                case 'SecurityError': //error from navigator.mediaDevices.getUserMedia or from the MediaRecorder.start
                    console.log("A SecurityError has occured.");
                    break;
                case 'TypeError': //error from navigator.mediaDevices.getUserMedia
                    console.log("A TypeError has occured.");
                    break;
                case 'InvalidStateError': //error from the MediaRecorder.start
                    console.log("An InvalidStateError has occured.");
                    break;
                case 'UnknownError': //error from the MediaRecorder.start
                    console.log("An UnknownError has occured.");
                    break;
                default:
                    console.log("AudioRecording: An error occured  " + error);
            }
        });    
    
}//end of start recording handler

function stopAudioRecording() {
    
    //stop the recording using the audio recording API
    console.log("Stopping Audio Recording...")
    
    audioRecorder.stop()
        .then(audioAsblob => { //stopping makes promise resolves to the blob file of the recorded audio
            console.log("stopped with audio Blob:"+ audioAsblob);
 
            let audioUrl = URL.createObjectURL(audioAsblob); // A DOMString containing an object URL that can be used to reference the contents of the specified source object.
            let audio = new Audio(audioUrl);
            
            audio.play() // returns a Promise which is resolved when playback has been successfully started.
            .then( () => {console.log('Audio is playing ')})
            .catch(error => {console.log('Playback error '+error.name)});
            
            
            
        })
        .catch(error => {
            //Error handling structure
            switch (error.name) {
                case 'InvalidStateError': //error from the MediaRecorder.stop
                    console.log("stopAudioRecording : An InvalidStateError has occured.");
                    break;
                default:
                    console.log("stopAudioRecording : An error occured " + error);
            }
 
        });
}// end of the stop recording function

/** Cancel the currently started audio recording */
function cancelAudioRecording() {
    
    console.log("Canceling audio...");
    //cancel the recording using the audio recording API
    audioRecorder.cancel();
 
    //Do something after audio recording is cancelled
}

/*----------------------------*/
/*      View code             */
/*----------------------------*/
$('#btnr').on('click',function(){
    startAudioRecording();
});

$('#btns').on('click',function(){
    stopAudioRecording();
});

$('#btnc').on('click',function(){
    cancelAudioRecording();
});

//end of onload event handler
});



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source