"use client"; import { useRef, useState } from "react"; import type { WordItem } from "../types"; type UsePkAudioOptions = { onGeneratedAudio: (word: WordItem) => void; }; export function usePkAudio({ onGeneratedAudio }: UsePkAudioOptions) { const [speechTip, setSpeechTip] = useState("点击开始后可朗读单词。"); const audioContextRef = useRef(null); function ensureAudioContext() { if (!audioContextRef.current) { audioContextRef.current = new window.AudioContext(); } return audioContextRef.current; } async function playTone(correct: boolean) { const audioContext = ensureAudioContext(); if (audioContext.state === "suspended") { await audioContext.resume(); } const osc = audioContext.createOscillator(); const gain = audioContext.createGain(); osc.connect(gain); gain.connect(audioContext.destination); const duration = correct ? 0.4 : 0.3; if (correct) { osc.frequency.setValueAtTime(523.25, audioContext.currentTime); osc.frequency.setValueAtTime(659.25, audioContext.currentTime + 0.1); osc.frequency.setValueAtTime(783.99, audioContext.currentTime + 0.2); gain.gain.setValueAtTime(0.25, audioContext.currentTime); gain.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.4); } else { osc.frequency.setValueAtTime(200, audioContext.currentTime); osc.frequency.setValueAtTime(150, audioContext.currentTime + 0.15); gain.gain.setValueAtTime(0.25, audioContext.currentTime); gain.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.3); } osc.start(audioContext.currentTime); osc.stop(audioContext.currentTime + duration); } async function speakWord(word: WordItem) { if (word.audio) { const audio = new Audio(word.audio); audio.play().catch(() => speakSystemWord(word.english)); setSpeechTip(`正在播放:${word.english}`); return; } try { setSpeechTip(`正在生成语音:${word.english}`); const response = await fetch(`/api/words/${word.id}/generate-audio`, { method: "POST" }); if (!response.ok) throw new Error("generate failed"); const data = (await response.json()) as { word: WordItem }; const audio = new Audio(data.word.audio); audio.play().catch(() => speakSystemWord(word.english)); setSpeechTip(`正在播放:${word.english}`); onGeneratedAudio(data.word); } catch { speakSystemWord(word.english); } } function speakSystemWord(word: string) { if (!("speechSynthesis" in window)) { setSpeechTip("当前浏览器不支持英文朗读,请老师带读。"); return; } window.speechSynthesis.cancel(); const utterance = new SpeechSynthesisUtterance(word); const voices = window.speechSynthesis.getVoices(); utterance.voice = voices.find((voice) => /^en[-_]?US/i.test(voice.lang)) ?? voices.find((voice) => /^en/i.test(voice.lang)) ?? null; utterance.lang = utterance.voice?.lang ?? "en-US"; utterance.rate = 0.85; utterance.pitch = 1; window.speechSynthesis.speak(utterance); setSpeechTip(`正在朗读:${word}`); } return { speechTip, setSpeechTip, playTone, speakWord, }; }