'use client'; import { useState, useRef, useCallback } from 'react'; export interface TTSRequest { text: string; accent?: 'us' | 'uk'; speed?: number; voice?: string; } export interface TTSResponse { audioUrl: string; duration: number; cacheHit: boolean; error?: string; } export interface AudioState { isPlaying: boolean; isLoading: boolean; error: string | null; currentAudio: string | null; } export function useAudio() { const [state, setState] = useState({ isPlaying: false, isLoading: false, error: null, currentAudio: null }); const audioRef = useRef(null); const currentRequestRef = useRef(null); // 更新狀態的輔助函數 const updateState = useCallback((updates: Partial) => { setState(prev => ({ ...prev, ...updates })); }, []); // 生成音頻 const generateAudio = useCallback(async (request: TTSRequest): Promise => { try { // 取消之前的請求 if (currentRequestRef.current) { currentRequestRef.current.abort(); } const controller = new AbortController(); currentRequestRef.current = controller; updateState({ isLoading: true, error: null }); const token = localStorage.getItem('token'); if (!token) { throw new Error('Authentication required'); } const response = await fetch('/api/audio/tts', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ text: request.text, accent: request.accent || 'us', speed: request.speed || 1.0, voice: request.voice || '' }), signal: controller.signal }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data: TTSResponse = await response.json(); if (data.error) { throw new Error(data.error); } updateState({ currentAudio: data.audioUrl }); return data.audioUrl; } catch (error) { if (error instanceof Error && error.name === 'AbortError') { return null; // 請求被取消 } const errorMessage = error instanceof Error ? error.message : 'Failed to generate audio'; updateState({ error: errorMessage }); return null; } finally { updateState({ isLoading: false }); currentRequestRef.current = null; } }, [updateState]); // 播放音頻 const playAudio = useCallback(async (audioUrl?: string, request?: TTSRequest) => { try { let urlToPlay = audioUrl; // 如果沒有提供 URL,嘗試生成 if (!urlToPlay && request) { urlToPlay = await generateAudio(request); if (!urlToPlay) return false; } if (!urlToPlay) { updateState({ error: 'No audio URL provided' }); return false; } // 創建新的音頻元素或使用現有的 let audio = audioRef.current; if (!audio) { audio = new Audio(); audioRef.current = audio; } // 設置音頻事件監聽器 const handleEnded = () => { updateState({ isPlaying: false }); audio?.removeEventListener('ended', handleEnded); audio?.removeEventListener('error', handleError); }; const handleError = () => { updateState({ isPlaying: false, error: 'Audio playback failed' }); audio?.removeEventListener('ended', handleEnded); audio?.removeEventListener('error', handleError); }; audio.addEventListener('ended', handleEnded); audio.addEventListener('error', handleError); // 設置音頻源並播放 audio.src = urlToPlay; await audio.play(); updateState({ isPlaying: true, error: null }); return true; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Failed to play audio'; updateState({ error: errorMessage, isPlaying: false }); return false; } }, [generateAudio, updateState]); // 暫停音頻 const pauseAudio = useCallback(() => { const audio = audioRef.current; if (audio) { audio.pause(); updateState({ isPlaying: false }); } }, [updateState]); // 停止音頻 const stopAudio = useCallback(() => { const audio = audioRef.current; if (audio) { audio.pause(); audio.currentTime = 0; updateState({ isPlaying: false }); } }, [updateState]); // 切換播放/暫停 const togglePlayPause = useCallback(async (audioUrl?: string, request?: TTSRequest) => { if (state.isPlaying) { pauseAudio(); } else { await playAudio(audioUrl, request); } }, [state.isPlaying, playAudio, pauseAudio]); // 設置音量 const setVolume = useCallback((volume: number) => { const audio = audioRef.current; if (audio) { audio.volume = Math.max(0, Math.min(1, volume)); } }, []); // 設置播放速度 const setPlaybackRate = useCallback((rate: number) => { const audio = audioRef.current; if (audio) { audio.playbackRate = Math.max(0.25, Math.min(4, rate)); } }, []); // 清除錯誤 const clearError = useCallback(() => { updateState({ error: null }); }, [updateState]); // 清理函數 const cleanup = useCallback(() => { if (currentRequestRef.current) { currentRequestRef.current.abort(); } stopAudio(); }, [stopAudio]); return { // 狀態 ...state, // 操作方法 generateAudio, playAudio, pauseAudio, stopAudio, togglePlayPause, setVolume, setPlaybackRate, clearError, cleanup }; }