import { useState, useEffect } from 'react' /** * Options for configuring speech synthesis. * @interface SpeechOptions */ interface SpeechOptions { /** The language for speech synthesis (e.g., 'en-US') */ lang?: string /** The pitch of the voice (0 to 2) */ pitch?: number /** The speed of the voice (0.1 to 10) */ rate?: number /** The voice to use for speech synthesis */ voice?: SpeechSynthesisVoice } /** * Custom hook to handle text-to-speech functionality. * * @param {SpeechSynthesisVoice} [initialVoice] - The initial voice to use for speech synthesis. * @returns {Object} An object containing methods to control speech synthesis and state variables. */ export const useTextToSpeech = (initialVoice?: SpeechSynthesisVoice) => { const [availableVoices, setAvailableVoices] = useState< SpeechSynthesisVoice[] >([]) const [currentVoice, setCurrentVoice] = useState< SpeechSynthesisVoice | undefined >(initialVoice) const [isSpeaking, setIsSpeaking] = useState(false) const [isPaused, setIsPaused] = useState(false) useEffect(() => { const updateVoices = () => { const voices = window.speechSynthesis.getVoices() setAvailableVoices(voices) // Set default voice to Google US English if available const googleVoice = voices.find( (voice) => voice.name === 'Google US English', ) if (googleVoice) { setCurrentVoice(googleVoice) } else { // Fallback to the first English voice if Google voice is not available const englishVoice = voices.find((voice) => voice.lang.startsWith('en-'), ) if (englishVoice) { setCurrentVoice(englishVoice) } } } updateVoices() window.speechSynthesis.onvoiceschanged = updateVoices return () => { window.speechSynthesis.onvoiceschanged = null } }, []) /** * Gets the list of available languages for speech synthesis. * @returns {string[]} An array of available language codes. */ const getAvailableLanguages = () => { return [...new Set(availableVoices.map((voice) => voice.lang))] } /** * Initiates speech synthesis for the given text. * * @param {string} text - The text to be spoken. * @param {SpeechOptions} [options={}] - Options for speech synthesis. * @param {Function} [onEnd] - Callback function to be called when speech ends. */ const speak = ( text: string, options: SpeechOptions = {}, onEnd?: () => void, ) => { const utterance = new SpeechSynthesisUtterance(text) utterance.lang = options.lang ?? 'en-US' utterance.pitch = options.pitch ?? 1 utterance.rate = options.rate ?? 1 utterance.voice = currentVoice ?? options.voice ?? null utterance.onend = () => { setIsSpeaking(false) setIsPaused(false) if (onEnd) { onEnd() } } if ('speechSynthesis' in window) { window.speechSynthesis.speak(utterance) setIsSpeaking(true) setIsPaused(false) } else { // Speech synthesis not supported - silently skip setIsSpeaking(false) } } /** * Changes the current voice used for speech synthesis. * @param {SpeechSynthesisVoice} voice - The new voice to use. */ const changeVoice = (voice: SpeechSynthesisVoice) => { setCurrentVoice(voice) } /** * Pauses the ongoing speech synthesis. */ const pause = () => { if (isSpeaking && !isPaused) { window.speechSynthesis.pause() setIsPaused(true) } } /** * Resumes the paused speech synthesis. */ const resume = () => { if (isSpeaking && isPaused) { window.speechSynthesis.resume() setIsPaused(false) } } /** * Cancels the ongoing speech synthesis. */ const cancel = () => { if (isSpeaking) { window.speechSynthesis.cancel() setIsSpeaking(false) setIsPaused(false) } } return { /** Initiates speech synthesis for the given text */ speak, /** Pauses the ongoing speech synthesis */ pause, /** Resumes the paused speech synthesis */ resume, /** Cancels the ongoing speech synthesis */ cancel, /** Indicates whether speech synthesis is currently active */ isSpeaking, /** Indicates whether speech synthesis is currently paused */ isPaused, /** Array of available voices for speech synthesis */ availableVoices, /** Changes the current voice used for speech synthesis */ changeVoice, /** The currently selected voice for speech synthesis */ currentVoice, /** Gets the list of available languages for speech synthesis */ getAvailableLanguages, } }