diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 4087e4f5..37bf0c07 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@reduxjs/toolkit": "^2.2.7", "chart.js": "^4.4.4", + "easy-speech": "^2.4.0", "i18next": "^23.15.1", "i18next-browser-languagedetector": "^8.0.0", "prop-types": "^15.8.1", @@ -3075,6 +3076,24 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true }, + "node_modules/easy-speech": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/easy-speech/-/easy-speech-2.4.0.tgz", + "integrity": "sha512-wpMv29DEoeP/eyXr4aXpDqd9DvlXl7aQs7BgfKbjGVxqkmQPgNmpbF5YULaTH5bc/5qrteg5MDfCD2Zd0qr4rQ==", + "funding": [ + { + "type": "GitHub", + "url": "https://github.com/sponsors/jankapunkt" + }, + { + "type": "PayPal", + "url": "https://paypal.me/kuesterjan" + } + ], + "engines": { + "node": ">= 14.x" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.11", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.11.tgz", diff --git a/frontend/package.json b/frontend/package.json index 83d531d6..9148831f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -21,6 +21,7 @@ "dependencies": { "@reduxjs/toolkit": "^2.2.7", "chart.js": "^4.4.4", + "easy-speech": "^2.4.0", "i18next": "^23.15.1", "i18next-browser-languagedetector": "^8.0.0", "prop-types": "^15.8.1", diff --git a/frontend/src/components/TextToSpeechButton.tsx b/frontend/src/components/TextToSpeechButton.tsx index 2ab7d1c2..cc062c87 100644 --- a/frontend/src/components/TextToSpeechButton.tsx +++ b/frontend/src/components/TextToSpeechButton.tsx @@ -1,6 +1,7 @@ import { useState } from 'react'; import Speaker from '../assets/speaker.svg?react'; import Stopspeech from '../assets/stopspeech.svg?react'; +import EasySpeech from 'easy-speech'; export default function SpeakButton({ text, @@ -14,26 +15,28 @@ export default function SpeakButton({ const [isSpeaking, setIsSpeaking] = useState(false); const [isSpeakHovered, setIsSpeakHovered] = useState(false); - const handleSpeakClick = (text: string) => { + const handleSpeakClick = async (text: string) => { if (isSpeaking) { - window.speechSynthesis.cancel(); + EasySpeech.cancel(); setIsSpeaking(false); return; } // Stop ongoing speech if already speaking - const utterance = new SpeechSynthesisUtterance(text); - setIsSpeaking(true); - - utterance.onend = () => { - setIsSpeaking(false); // Reset when speech ends - }; - - utterance.onerror = () => { - console.error('Speech synthesis failed.'); - setIsSpeaking(false); - }; - - window.speechSynthesis.speak(utterance); + try { + await EasySpeech.init(); // Initialize EasySpeech + setIsSpeaking(true); + + EasySpeech.speak({ + text, + onend: () => setIsSpeaking(false), // Reset when speech ends + onerror: () => { + console.error('Speech synthesis failed.'); + setIsSpeaking(false); + }, + }); + } catch (error) { + console.error('Failed to initialize speech synthesis', error); + } }; return (