How to create a sound fadeout effect with SpeechSynthesis API?

79 Views Asked by At

I'm trying to create a fadeout for the current SpeechSynthesis text being spoken(i.e. gradually decrement the volume until it is 0), and after that it should stop playing(once volume reaches 0) and start the next SpeechSynthesis text spoken that is in the queue. However, it seems as if the volume does indeed decrement while the text is being spoken(i tested it with the console log statements) but in reality the volume is the same(what I hear). Only for the next text is the volume 0(which I plan on changing back to 1 once I fixed this issue).

import React, { useEffect, useState } from "react";
import VoiceMessages from "./messages";
const VoiceAssistantProvider = ({ children }) => {
  // [public, private]
  const [isUsable, setIsUsable] = useState(false);
  // [public, private]
  const [isSpeaking, setIsSpeaking] = useState(false);
  // [public, public]
  const [isActive, setIsActive] = useState(false);
  // [private, private]
  const [voice, setVoice] = useState(null);
  // [public, private]
  const [voiceAssistant, setVoiceAssistant] = useState(null);

  // [private, private]
  const [fadeoutTime, setFadeoutTime] = useState(2000);
  // [private, private]
  const [startFadeout, setStartFadeout] = useState(false);

  // Handle voices once loaded
  const handleVoicesLoaded = () => {
    const voices = speechSynthesis.getVoices();
    setVoice(voices[2]);
  };

  // Once voice is set up and voiceAssistant is set up as well, set the voice
  useEffect(() => {
    if (voice && voiceAssistant) {
      voiceAssistant.voice = voice;
    }
  }, [voiceAssistant, voice]);

  // Run once to set up voiceAssistant
  useEffect(() => {
    if ("speechSynthesis" in window) {
      setIsUsable(true);
      setVoiceAssistant(new SpeechSynthesisUtterance());
      window.speechSynthesis.onvoiceschanged = handleVoicesLoaded;
    }
  }, []);

  // Start the fadeout
  useEffect(() => {
    if (startFadeout) {
      var volumeTimeout;
      const step = (voiceAssistant.volume * 100) / fadeoutTime;

      const fadeStep = () => {
        console.log(`Inside step`);
        voiceAssistant.volume -= step;
        console.log("Volume is: ", voiceAssistant.volume);
        if (voiceAssistant.volume > 0) {
          volumeTimeout = setTimeout(fadeStep, 100);
        } else {
          speechSynthesis.cancel();
          setIsSpeaking(false);
          setStartFadeout(false);
        }
      };
      fadeStep();
      return () => clearTimeout(volumeTimeout);
    }
  }, [startFadeout]);

  // Handle isActive change
  useEffect(() => {
    if (isSpeaking) {
      setStartFadeout(true);
    }
    if (isActive) {
      voiceAssistant.text = VoiceMessages.InitializeVoiceAssistant;
      speechSynthesis.speak(voiceAssistant);
      setIsSpeaking(true);
    } else if (!isActive && isUsable) {
      voiceAssistant.text = VoiceMessages.StopVoiceAssistant;
      speechSynthesis.speak(voiceAssistant);
      setIsSpeaking(true);
    }
  }, [isActive]);

  // Add event listener for voiceAssistant for the end of message.
  useEffect(() => {
    const handleEnd = () => {
      setIsSpeaking(false);
    };

    if (voiceAssistant) {
      voiceAssistant.addEventListener("end", handleEnd);
      return () => {
        voiceAssistant.removeEventListener("end", handleEnd);
      };
    }
  }, [voiceAssistant]);

  useEffect(() => {
    setVoiceAI({
      voiceAssistant,
      isUsable,
      isSpeaking,
      isActive,
      voice,
      setIsActive,
    });
  }, [voiceAssistant, isUsable, isSpeaking, isActive, voice]);

  const [voiceAI, setVoiceAI] = useState({
    voiceAssistant,
    isUsable,
    isSpeaking,
    isActive,
    voice,
    setIsActive,
  });

  const wrappedChildren = React.Children.map(children, (child) => {
    return React.cloneElement(child, { voiceAI });
  });

  return <>{wrappedChildren}</>;
};

export default VoiceAssistantProvider;

0

There are 0 best solutions below