I want to record a video from some script running on an HTML canvas and simultaneously record the audio from the microphone. I am using canvas.captureStream() to create a video stream from the canvas. Then I create an audio stream from getUserMedia() and mix both streams in a single one which is passed to a new MediaRecorder.
The problem is that, by default, no frame is captured in the canvas stream if nothing is painted to the canvas. So the video and audio are desynchronized in the parts where the canvas is inactive.
The code I am using looks like this:
let recording = false;
let mediaRecorder;
let chunks = [];
let newStream, canvasStream
let constrains = {
audio: {
channelCount: { ideal: 2, min: 1 },
sampleRate: 48000,
sampleSize: 16,
volume: 1,
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true,
},
video: false
};
recButton.addEventListener("click", () => {
recording = !recording;
if (recording) {
recButton.textContent = "Click to Stop";
recButton.style.color = "red";
canvasStream = canvas.captureStream(40); // ---> captureStream()
navigator.mediaDevices.getUserMedia(constrains) // ---> audioStream
.then(audioStream => {
canvasStream.addTrack(audioStream.getTracks()[0]); // --> joint the two streams
mediaRecorder = new MediaRecorder(canvasStream, {
mimeType: 'video/webm; codecs=vp9',
// ignoreMutedMedia: false
});
mediaRecorder.ondataavailable = e => {
if (e.data.size > 0) {
chunks.push(e.data); // chunks = []; already defined above....
}
};
mediaRecorder.start();
})
.catch(err => {
console.log(err.name, err.message);
});
} else {
recButton.textContent = "Record";
recButton.style.color = "black";
mediaRecorder.stop()
stopTracks(canvasStream); // function stop defined below
console.log(mediaRecorder.state)
setTimeout(() => { // why this is necessary?
const blob = new Blob(chunks, {
type: "video/mp4"
});
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "recording.mp4";
a.click();
URL.revokeObjectURL(url);
}, 200);
}
});
let stopTracks = stream => stream.getTracks().forEach(track => track.stop());