import { useState, useRef, useEffect } from 'react'; import { api } from '../lib/api'; import type { TTSStylePreset, TTSProvider } from '../types/api'; interface VoicePreviewButtonProps { voiceName: string; language: string; disabled?: boolean; provider?: TTSProvider; model?: string; speed?: number; stylePreset?: TTSStylePreset; customStylePrompt?: string; stability?: number; similarityBoost?: number; } export function VoicePreviewButton({ voiceName, language, disabled, provider, model, speed, stylePreset, customStylePrompt, stability, similarityBoost, }: VoicePreviewButtonProps) { const [isLoading, setIsLoading] = useState(false); const [isPlaying, setIsPlaying] = useState(false); const [error, setError] = useState(null); const audioRef = useRef(null); const audioUrlRef = useRef(null); // Clear cached audio when any TTS setting changes useEffect(() => { // Stop any playing audio if (audioRef.current) { audioRef.current.pause(); audioRef.current = null; } // Revoke old URL if (audioUrlRef.current) { URL.revokeObjectURL(audioUrlRef.current); audioUrlRef.current = null; } setIsPlaying(false); setError(null); }, [voiceName, language, provider, model, speed, stylePreset, customStylePrompt, stability, similarityBoost]); const handlePreview = async () => { setError(null); // If already playing, stop if (isPlaying && audioRef.current) { audioRef.current.pause(); audioRef.current.currentTime = 0; setIsPlaying(false); return; } // If we have cached audio, play it if (audioUrlRef.current && audioRef.current) { audioRef.current.play(); setIsPlaying(true); return; } // Fetch new audio setIsLoading(true); try { const blob = await api.previewVoice( voiceName, language, model, speed, stylePreset, customStylePrompt, provider, stability, similarityBoost, ); const url = URL.createObjectURL(blob); // Clean up old URL if exists if (audioUrlRef.current) { URL.revokeObjectURL(audioUrlRef.current); } audioUrlRef.current = url; // Create and play audio const audio = new Audio(url); audioRef.current = audio; audio.onended = () => { setIsPlaying(false); }; audio.onerror = () => { setError('Failed to play audio'); setIsPlaying(false); }; await audio.play(); setIsPlaying(true); } catch (err) { const errorMessage = err instanceof Error ? err.message : 'Failed to generate preview'; setError(errorMessage); console.error('Voice preview error:', err); } finally { setIsLoading(false); } }; return (
{error && {error}}
); }