"use client"; import { useCallback, useEffect, useRef } from "react"; import { plainTextForSpeech } from "@/lib/voiceText"; function pickKoreanVoice(): SpeechSynthesisVoice | null { if (typeof window === "undefined" || !window.speechSynthesis) return null; return window.speechSynthesis.getVoices().find((v) => v.lang.startsWith("ko")) ?? null; } export function useBrowserTts() { const utterRef = useRef(null); useEffect(() => { if (typeof window === "undefined" || !window.speechSynthesis) return; const onVoices = () => pickKoreanVoice(); window.speechSynthesis.addEventListener("voiceschanged", onVoices); return () => window.speechSynthesis.removeEventListener("voiceschanged", onVoices); }, []); const stop = useCallback(() => { window.speechSynthesis?.cancel(); utterRef.current = null; }, []); const speak = useCallback( (text: string, enabled: boolean): Promise => { if (!enabled || !text.trim() || typeof window === "undefined" || !window.speechSynthesis) { return Promise.resolve(); } stop(); const spoken = plainTextForSpeech(text); if (!spoken) return Promise.resolve(); return new Promise((resolve) => { const utter = new SpeechSynthesisUtterance(spoken); utter.lang = "ko-KR"; utter.rate = 1; const ko = pickKoreanVoice(); if (ko) utter.voice = ko; utter.onend = () => resolve(); utter.onerror = () => resolve(); utterRef.current = utter; window.speechSynthesis.resume(); window.speechSynthesis.speak(utter); }); }, [stop], ); return { speak, stop, supported: typeof window !== "undefined" && !!window.speechSynthesis }; }