Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | 5x 23x 23x 23x 23x 20x 20x 20x 20x 20x 20x 20x 20x 20x 20x 20x 23x 18x 17x 23x 20x 20x 2x 2x 18x 18x 13x 13x 13x 13x 13x 23x 42x 21x 23x 20x 20x 23x | /* eslint-disable no-irregular-whitespace */
// NOTE: this is a custom hook and not a component
import { useEffect, useRef, useState } from "react";
import { useSelector } from "react-redux";
const useTextToSpeech = () => {
const { settings } = useSelector((state) => state.StudentQuestionPage);
const utteranceRef = useRef(null);
const [speaking, setSpeaking] = useState(false);
useEffect(() => {
// Initialize utterance
const utterance = new SpeechSynthesisUtterance();
utteranceRef.current = utterance;
utterance.voice = getVoiceByName("Google US English");
// var voices = speechSynthesis.getVoices();
// console.log(voices);
// voices.forEach(function (voice, index) {
// console.log(voice.name);
// // console.log("Voice " + index.toString());
// // console.log("Name: " + voice.name);
// // console.log("Lang: " + voice.lang);
// // console.log("Default: " + voice.default);
// // console.log("URI: " + voice.voiceURI);
// // console.log("--------------------------------------");
// });
// Event listener for speech start and end
const onStart = () => setSpeaking(true);
const onEnd = () => setSpeaking(false);
utterance.addEventListener("start", onStart);
utterance.addEventListener("end", onEnd);
return () => {
// Clean up: Remove event listeners
utterance.removeEventListener("start", onStart);
utterance.removeEventListener("end", onEnd);
// Cancel any ongoing speech when component unmounts
cancelSpeech();
};
}, []);
const normalizeSpeechText = (value = "") => {
if (typeof value !== "string") return "";
return (
value
// remove HTML tags
.replace(/<[^>]*>/g, "")
// convert __blank__ placeholders to spoken word
.replace(/__blank__/g, "blank")
// normalize whitespace
.replace(/\s+/g, " ")
.trim()
);
};
const speak = (text) => {
cancelSpeech();
if (typeof window === "undefined" || !window.speechSynthesis) {
console.error("Speech synthesis not supported.");
return;
}
const cleanText = normalizeSpeechText(text);
if (!cleanText) return;
const utterance = utteranceRef.current;
utterance.text = cleanText;
utterance.rate = settings.voice.speed;
utterance.volume = settings.voice.volume;
window.speechSynthesis.speak(utterance);
};
const cancelSpeech = () => {
if (typeof window !== "undefined" && window.speechSynthesis) {
window.speechSynthesis.cancel();
}
};
const getVoiceByName = (voiceName) => {
const voices = window.speechSynthesis.getVoices();
return voices.find((voice) => voice.name === voiceName);
};
return { speak, cancelSpeech, speaking };
};
export default useTextToSpeech;
// Microsoft David - English (United States)
// Microsoft Mark - English (United States)
// Microsoft Zira - English (United States)
// Google Deutsch
// Google US English
// Google UK English Female
// Google UK English Male
// Google español
// Google español de Estados Unidos
// Google français
// Google हिन्दी
// Google Bahasa Indonesia
// Google italiano
// Google 日本語
// Google 한국의
// Google Nederlands
// Google polski
// Google português do Brasil
// Google русский
// Google 普通话(中国大陆)//space included
// Google 粤語(香港)//space included
// Google 國語(臺灣)//space included
|