Skip to content

Commit

Permalink
speakButton
Browse files Browse the repository at this point in the history
  • Loading branch information
moikale committed Nov 27, 2024
1 parent 64e14dc commit b8ff89d
Showing 1 changed file with 72 additions and 148 deletions.
220 changes: 72 additions & 148 deletions src/components/SpeakerButton/SpeakerButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,179 +5,103 @@ interface SpeakerButtonProps {
text: string;
}

// Liste bekannter männlicher Stimmen
const MALE_VOICE_NAMES = [
'deutsch male', 'german male',
'markus', 'hans', 'stefan', 'klaus',
'microsoft stefan', 'microsoft markus',
'google deutsch männlich'
];
// Liste von bekannten männlichen Stimmen
const MALE_VOICE_KEYWORDS = ['male', 'männlich', 'stefan', 'hans', 'michael', 'klaus'];

export const SpeakerButton = ({ text }: SpeakerButtonProps) => {
const [isPlaying, setIsPlaying] = useState(false);
const [germanVoice, setGermanVoice] = useState<SpeechSynthesisVoice | null>(null);
const [isSupported, setIsSupported] = useState(false);
const utteranceRef = useRef<SpeechSynthesisUtterance | null>(null);

useEffect(() => {
// Prüfe ob Speech Synthesis unterstützt wird
if (typeof window !== 'undefined' && 'speechSynthesis' in window && 'SpeechSynthesisUtterance' in window) {
setIsSupported(true);
// Funktion zum Laden der deutschen Männerstimme
const loadGermanVoice = () => {
const voices = window.speechSynthesis.getVoices();
const germanVoices = voices.filter(voice => voice.lang.startsWith('de'));

const loadGermanVoice = () => {
try {
const voices = window.speechSynthesis.getVoices();

// Filter für deutsche Stimmen
const germanVoices = voices.filter(voice =>
voice.lang.startsWith('de') ||
voice.lang.startsWith('de-DE')
);

if (germanVoices.length > 0) {
// Suche nach männlicher Stimme
const maleVoice = germanVoices.find(voice =>
MALE_VOICE_NAMES.some(name =>
voice.name.toLowerCase().includes(name.toLowerCase())
)
);

// Versuche die beste Stimme zu finden
const selectedVoice = maleVoice ||
// Fallback: Versuche eine Stimme mit "Deutsch" im Namen zu finden
germanVoices.find(voice => voice.name.toLowerCase().includes('deutsch')) ||
// Sonst nimm die erste deutsche Stimme
germanVoices[0];

console.log('Verfügbare deutsche Stimmen:', germanVoices.map(v => v.name).join(', '));
console.log('Gewählte Stimme:', selectedVoice.name);

setGermanVoice(selectedVoice);
} else {
console.log('Keine deutsche Stimme gefunden');
}
} catch (error) {
console.error('Fehler beim Laden der Stimmen:', error);
}
};

// Versuche Stimmen sofort zu laden
loadGermanVoice();

// Registriere Event-Listener für das Laden der Stimmen
if (window.speechSynthesis.onvoiceschanged !== undefined) {
window.speechSynthesis.onvoiceschanged = loadGermanVoice;
if (germanVoices.length > 0) {
// Suche nach männlicher Stimme
const maleVoice = germanVoices.find(voice =>
MALE_VOICE_KEYWORDS.some(keyword =>
voice.name.toLowerCase().includes(keyword)
)
);

// Wenn keine explizit männliche Stimme gefunden wurde,
// bevorzuge Microsoft oder Google Stimmen
const selectedVoice = maleVoice || germanVoices.find(
voice => voice.name.includes('Microsoft') || voice.name.includes('Google')
) || germanVoices[0];

setGermanVoice(selectedVoice);
console.log('Selected German voice:', selectedVoice.name);
}
};

return () => {
try {
window.speechSynthesis.cancel();
if (window.speechSynthesis.onvoiceschanged !== undefined) {
window.speechSynthesis.onvoiceschanged = null;
}
} catch (error) {
console.error('Fehler beim Cleanup:', error);
}
};
} else {
console.log('Speech Synthesis wird nicht unterstützt');
}
// Lade Stimmen initial und wenn sie sich ändern
loadGermanVoice();
window.speechSynthesis.onvoiceschanged = loadGermanVoice;

// Cleanup
return () => {
window.speechSynthesis.cancel();
window.speechSynthesis.onvoiceschanged = null;
};
}, []);

const handlePlay = () => {
if (!isSupported || !germanVoice) {
setIsPlaying(!isPlaying);
setTimeout(() => setIsPlaying(false), 500);
const speak = () => {
if (!window.speechSynthesis) {
console.error('Speech synthesis not supported');
return;
}

try {
// Stoppe vorherige Wiedergabe
if (isPlaying) {
window.speechSynthesis.cancel();
setIsPlaying(false);
return;
}

// Text in Sätze aufteilen für natürlichere Pausen
const sentences = text.match(/[^.!?]+[.!?]+/g) || [text];
let currentSentence = 0;

const speakSentence = (sentence: string) => {
try {
const utterance = new SpeechSynthesisUtterance(sentence.trim());
utterance.voice = germanVoice;

// Optimierte Einstellungen für männlichere Stimme
utterance.rate = 0.85; // Etwas langsamer
utterance.pitch = 0.8; // Tiefere Stimme
utterance.volume = 1.0; // Volle Lautstärke

utterance.onstart = () => setIsPlaying(true);
utterance.onend = () => {
currentSentence++;
if (currentSentence < sentences.length) {
// Kurze Pause zwischen Sätzen
setTimeout(() => {
speakSentence(sentences[currentSentence]);
}, 300);
} else {
setIsPlaying(false);
}
};
// Stoppe vorherige Sprachausgabe
window.speechSynthesis.cancel();

const utterance = new SpeechSynthesisUtterance(text);
utterance.lang = 'de-DE';
utterance.rate = 0.9; // 10% langsamer für bessere Verständlichkeit
utterance.pitch = 0.6; // Leicht tiefere Stimme (0-2, normal ist 1.0)
utterance.volume = 1.0; // Maximale Lautstärke

if (germanVoice) {
utterance.voice = germanVoice;
}

utterance.onerror = (event) => {
console.error('Fehler bei der Sprachausgabe:', event);
setIsPlaying(false);
};
utterance.onstart = () => setIsPlaying(true);
utterance.onend = () => setIsPlaying(false);
utterance.onerror = (event) => {
console.error('Speech synthesis error:', event);
setIsPlaying(false);
};

utteranceRef.current = utterance;
window.speechSynthesis.speak(utterance);
} catch (error) {
console.error('Fehler beim Sprechen eines Satzes:', error);
setIsPlaying(false);
}
};
utteranceRef.current = utterance;
window.speechSynthesis.speak(utterance);
};

speakSentence(sentences[0]);
} catch (error) {
console.error('Fehler beim Starten der Sprachausgabe:', error);
setIsPlaying(false);
}
const stopSpeaking = () => {
window.speechSynthesis.cancel();
setIsPlaying(false);
};

return (
<div className="speaker-controls">
<button
className={`speaker-button ${isPlaying ? 'playing' : ''} ${!isSupported || !germanVoice ? 'unsupported' : ''}`}
onClick={handlePlay}
className={`speaker-button ${isPlaying ? 'playing' : ''}`}
onClick={isPlaying ? stopSpeaking : speak}
aria-label={isPlaying ? 'Stoppe Vorlesen' : 'Text vorlesen'}
title={!isSupported || !germanVoice ? 'Sprachausgabe nicht verfügbar' : (isPlaying ? 'Stoppe Vorlesen' : 'Text vorlesen')}
title={isPlaying ? 'Stoppe Vorlesen' : 'Text vorlesen'}
>
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
{isPlaying ? (
// Pause Icon
<>
<line x1="6" y1="4" x2="6" y2="20" />
<line x1="18" y1="4" x2="18" y2="20" />
</>
) : (
// Speaker Icon
<>
<polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5" />
<path d="M15.54 8.46a5 5 0 0 1 0 7.07" />
<path d="M19.07 4.93a10 10 0 0 1 0 14.14" />
</>
)}
</svg>
{isPlaying ? (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path d="M6 4h4v16H6zm8 0h4v16h-4z"/>
</svg>
) : (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"/>
</svg>
)}
</button>
</div>
);
};
};

0 comments on commit b8ff89d

Please sign in to comment.