Search code examples
javascripthtmlcssanimationwebspeech-api

How to make Animated Web Speech API UI in HTML


I have to create a animation Like Google.com Dekstop Mic shows (i.e. scaling of the mic border according to the loudness of voice). I have used the Web Speech API with reference from here (MDN) which shows how we can change the background colour of the webpage using our voice, it work's fine but I want to add Animation Like Google's site( mentioned above).
I have searched a lot to find a way to achieve this animation but I was unable to find this. So I am asking here as this is the best place where I can get my answer :)

Thanks a lot in advance for helping me out with this.


Solution

  • I'm not expert in this area but I followed the example in MDN and here is the result.

    Beside the setup, the key point here is analyser.getByteFrequencyData which gives us the decibel levels.

    In order to simplify the code, I took the highest decibel level in the array (Math.max.apply(null, dataArray)) but you can fine tuning it by average or any other calculation you like.

    Demo

    let audioCtx = new (window.AudioContext || window.webkitAudioContext)();
    let distortion = audioCtx.createWaveShaper();
    let gainNode = audioCtx.createGain();
    let biquadFilter = audioCtx.createBiquadFilter();
    let analyser = audioCtx.createAnalyser();
    analyser.minDecibels = -90;
    analyser.maxDecibels = -10;
    
    analyser.fftSize = 256;
    
    const mic = document.querySelector('.mic');
    let isListening = false;
    let tracks = [];
    
    if (!navigator.mediaDevices.getUserMedia) {
      alert('getUserMedia not supported on your browser!');
    }
    
    mic.addEventListener('click', async () => {
      if (!isListening) {
        try {
          const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
          isListening = true;
    
          tracks = stream.getTracks();
          source = audioCtx.createMediaStreamSource(stream);
          source.connect(distortion);
          distortion.connect(biquadFilter);
          biquadFilter.connect(gainNode);
          gainNode.connect(analyser);
          analyser.connect(audioCtx.destination);
    
          requestAnimationFrame(function log() {
            let bufferLength = analyser.frequencyBinCount;
            let dataArray = new Uint8Array(bufferLength);
            analyser.getByteFrequencyData(dataArray);
            const level = Math.max.apply(null, dataArray);
            document.querySelector('#level span').textContent = level;
            mic.style.setProperty('--border', `${level / 5}px`);
            requestAnimationFrame(log);
          });
        } catch (err) {
          console.log('The following gUM error occured: ' + err);
        }
      } else {
        isListening = false;
        tracks.forEach((track) => {
          track.stop();
        });
      }
    });
    
    body {
      margin: 0;
      height: 100vh;
      position: relative;
    }
    
    .content {
      height: 100%;
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      gap: 20px;
    }
    
    .mic {
      background: #fff;
      width: 50px;
      height: 50px;
      border: 1px solid #eee;
      border-radius: 100%;
      bottom: 0;
      box-shadow: 0 2px 5px var(--border) rgb(0 0 0 / 10%);
      cursor: pointer;
      display: inline-flex;
      align-items: center;
      justify-content: center;
    }
    
    <html>
      <head>
        <meta charset="UTF-8" />
        <link rel="stylesheet" type="text/css" href="styles.css" />
        <link
          rel="stylesheet"
          href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css"
          integrity="sha512-Fo3rlrZj/k7ujTnHg4CGR2D7kSs0v4LLanw2qksYuRlEzO+tcaEPQogQ0KaoGN26/zrn20ImR1DfuLWnOo7aBA=="
          crossorigin="anonymous"
          referrerpolicy="no-referrer"
        />
      </head>
      <body>
        <div class="content">
          <div class="mic">
            <i class="fas fa-microphone"></i>
          </div>
          <div id="level">Level: <span></span></div>
        </div>
        <script src="script.js"></script>
      </body>
    </html>