I am creating a simple "record my screen" app.
Everything works fine until I want to add the functionality of being able to activate the Mic after the recording has started.
This is possible using an AudioDestination as a proxy:
const fullStream = new MediaStream();
const audioCtx = new AudioContext();
const audioDestination = audioCtx.createMediaStreamDestination();
const fullAudioTrack = audioDestination.stream.getAudioTracks()[0];
fullStream.addTrack(fullAudioTrack);
const mediaRecorder = new MediaRecorder(fullStream);
mediaRecorder.start();
And later we can do:
const micStream =
await navigator.mediaDevices.getUserMedia({
video: false,
audio: true
});
const micSource = audioCtx.createMediaStreamSource(micStream);
micSource.connect(audioDestination);
This works fine in Firefox, but not in Chrome.
In Chrome (v116.0.5845.187) if I start the MediaRecorder while the AudioDestination has still not any source connected then the video is not recorder properly. If for example I record 10 seconds only a few seconds are recorded. And if I activate the mic in the middle of the recording, only the portion with the mic activated is recorded.
Working example:
const videoElement = document.querySelector("#video-wrapper video");
async function record() {
const fullStream = new MediaStream();
// Add track for fullAudio
const audioCtx = new AudioContext();
const audioDestination = audioCtx.createMediaStreamDestination();
const fullAudioTrack = audioDestination.stream.getAudioTracks()[0];
fullStream.addTrack(fullAudioTrack);
// Getting screenStream
const screenStream = await navigator.mediaDevices.getDisplayMedia({ video: true, audio: false });
fullStream.addTrack(screenStream.getVideoTracks()[0]);
// Getting micStream
const micStream = await navigator.mediaDevices.getUserMedia({ video: false, audio: true });
const micSource = audioCtx.createMediaStreamSource(micStream);
// micSource.connect(audioDestination); // If I un-comment this line, the recording is made properly. But this connection has to be made after
// Play realtime stream
videoElement.srcObject = fullStream;
videoElement.play();
// Set up MediaRecorder
const recordedChunks = [];
const mediaRecorder = new MediaRecorder(fullStream, { mimeType: "video/webm" });
mediaRecorder.ondataavailable = (event) => {
recordedChunks.push(event.data);
};
mediaRecorder.onstop = () => {
const blob = new Blob(recordedChunks, { type: "video/webm" });
// Play recorded blob
playBlob(blob);
};
mediaRecorder.start();
// Stop MediaRecorder after 10 seconds
setTimeout(() => {
mediaRecorder.stop();
}, 10000);
}
function playBlob(blob) {
const videoURL = window.URL.createObjectURL(blob);
videoElement.pause();
videoElement.srcObject = null;
videoElement.src = videoURL;
videoElement.muted = false;
videoElement.play();
}
document.querySelector("#record-button").addEventListener("click", () => {
record();
document.querySelector("#record-button").setAttribute("disabled", true);
});
#video-wrapper {
position: relative;
width: 500px;
}
#video-wrapper video {
width: 100% !important;
height: auto !important;
}
<!doctype html>
<html lang="en">
<head>
<meta charset=utf-8>
<title>Testing MediaRecorder</title>
<link rel="stylesheet" href="./style.css">
</head>
<body>
<div id="video-wrapper">
<video controls></video>
</div>
<button id="record-button">Record</button>
</body>
<script src="./script.js"></script>
</html>
The snippet is not working due permission. Try the more interactive version in CodePen:
Whenever the mic is connected before clicking in "Start recording". The recording is made properly. Any other combination fails in Chrome. But works in Firefox.
I found a workaround by attaching a silent audio track to the AudioDestination. So there is always one audio track.
This can be done creating an oscillator to a GainNode whose gain is set to 0, and connect this GainNode to your destination stream.
// Adding a silent audioTrack to overpass chrome bug: https://bugs.chromium.org/p/chromium/issues/detail?id=1490888
const oscillator = audioCtx.createOscillator();
const gainNode = audioCtx.createGain();
gainNode.gain.value = 0;
oscillator.connect(gainNode);
gainNode.connect(audioDestination);