'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 |
|---|
