From 4ab7990e5d7aef1c82681367801f8070bd7581e1 Mon Sep 17 00:00:00 2001 From: Nan Kyung Lee Date: Tue, 24 Mar 2026 15:57:37 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=EC=A4=91=EA=B5=AD=EC=96=B4=20TTS=20?= =?UTF-8?q?=E2=86=92=20Google=20Translate=20TTS=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20(=EA=B3=A0=ED=92=88=EC=A7=88=20=EB=B0=9C=EC=9D=8C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/korea/OpsGuideModal.tsx | 37 ++++++------------- 1 file changed, 12 insertions(+), 25 deletions(-) diff --git a/frontend/src/components/korea/OpsGuideModal.tsx b/frontend/src/components/korea/OpsGuideModal.tsx index fd0fd58..9625230 100644 --- a/frontend/src/components/korea/OpsGuideModal.tsx +++ b/frontend/src/components/korea/OpsGuideModal.tsx @@ -143,32 +143,19 @@ export function OpsGuideModal({ ships, onClose, onFlyTo, onRouteSelect }: Props) navigator.clipboard.writeText(text).then(() => { setCopiedIdx(idx); setTimeout(() => setCopiedIdx(null), 1500); }); }; + const audioRef = useRef(null); + const speakChinese = useCallback((text: string, idx: number) => { - if (typeof window === 'undefined' || !window.speechSynthesis) return; - window.speechSynthesis.cancel(); - const utter = new SpeechSynthesisUtterance(text); - utter.lang = 'zh-CN'; - utter.rate = 0.8; - utter.pitch = 1.0; - utter.volume = 1; - const voices = window.speechSynthesis.getVoices(); - const zhVoice = voices.find(v => v.lang === 'zh-CN') || voices.find(v => v.lang.startsWith('zh')); - if (zhVoice) utter.voice = zhVoice; - // Chrome bug workaround: pause/resume keepalive to prevent cutoff - let keepAlive: ReturnType | null = null; - utter.onstart = () => { - setSpeakingIdx(idx); - keepAlive = setInterval(() => { - if (window.speechSynthesis.speaking && !window.speechSynthesis.paused) { - window.speechSynthesis.pause(); - window.speechSynthesis.resume(); - } - }, 5000); - }; - const cleanup = () => { setSpeakingIdx(null); if (keepAlive) clearInterval(keepAlive); }; - utter.onend = cleanup; - utter.onerror = cleanup; - window.speechSynthesis.speak(utter); + // Stop previous + if (audioRef.current) { audioRef.current.pause(); audioRef.current = null; } + setSpeakingIdx(idx); + const encoded = encodeURIComponent(text); + const url = `https://translate.google.com/translate_tts?ie=UTF-8&client=tw-ob&tl=zh-CN&q=${encoded}`; + const audio = new Audio(url); + audioRef.current = audio; + audio.onended = () => setSpeakingIdx(null); + audio.onerror = () => setSpeakingIdx(null); + audio.play().catch(() => setSpeakingIdx(null)); }, []); const handleSuspectClick = (s: SuspectVessel) => {