Search code examples
javascriptaudio

Detect silence in audio recording


There are similar questions for Java and iOS, but I'm wondering about detecting silence in javascript for audio recordings via getUserMedia(). So given:

navigator.mediaDevices.getUserMedia({ audio: true })
  .then(stream => {
    const mediaRecorder = new MediaRecorder(stream);
    mediaRecorder.start();

    const audioChunks = [];
    mediaRecorder.addEventListener("dataavailable", event => {
      audioChunks.push(event.data);
    });

    mediaRecorder.addEventListener("stop", () => {
      const audioBlob = new Blob(audioChunks);
      const audioUrl = URL.createObjectURL(audioBlob);
      const audio = new Audio(audioUrl);
      audio.play();
    });
  });

I'm wondering if there is anything that can be checked on the Blob, URL, or Audio objects in the stop event for an absence of audio. In the case of a bad microphone or a virtual device selected - anything along those lines. I was previously checking the blob's size, but silent audio still has a filesize. I can do this on the backend via ffmpeg, but hoping there is a way in pure JS to simplify.


Solution

  • With this solution inspired by Visualizations with Web Audio API, you can set minimal required decibels and detect if anything was recorded.

    const MIN_DECIBELS = -45;
    
    navigator.mediaDevices.getUserMedia({ audio: true })
      .then(stream => {
        const mediaRecorder = new MediaRecorder(stream);
        mediaRecorder.start();
    
        const audioChunks = [];
        mediaRecorder.addEventListener("dataavailable", event => {
          audioChunks.push(event.data);
        });
    
        const audioContext = new AudioContext();
        const audioStreamSource = audioContext.createMediaStreamSource(stream);
        const analyser = audioContext.createAnalyser();
        analyser.minDecibels = MIN_DECIBELS;
        audioStreamSource.connect(analyser);
    
        const bufferLength = analyser.frequencyBinCount;
        const domainData = new Uint8Array(bufferLength);
    
        let soundDetected = false;
    
        const detectSound = () => {
          if (soundDetected) {
            return
          }
    
          analyser.getByteFrequencyData(domainData); 
    
          for (let i = 0; i < bufferLength; i++) {
            const value = domainData[i];
    
            if (domainData[i] > 0) {
              soundDetected = true
            }
          }
    
          window.requestAnimationFrame(detectSound);
        };
    
        window.requestAnimationFrame(detectSound);
    
        mediaRecorder.addEventListener("stop", () => {
          const audioBlob = new Blob(audioChunks);
          const audioUrl = URL.createObjectURL(audioBlob);
          const audio = new Audio(audioUrl);
          audio.play();
          
          console.log({ soundDetected });
        });
      });