javascriptgoogle-chromemediarecorder

MediaRecorder issue in Chrome when using AudioDestination with no StreamSource connected


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.


Solution

  • 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);
    

    Source