import { useEffect, useRef, useState } from "react"; import { Button } from "@/components/ui/button"; import { Loader2, Pause, Play } from "lucide-react"; interface ChatVoicePlayerProps { text: string; autoPlay?: boolean; } type PlayerStatus = "idle" | "loading" | "playing" | "paused"; export function ChatVoicePlayer({ text, autoPlay = false }: ChatVoicePlayerProps) { const [status, setStatus] = useState("loading"); const [audioUrl, setAudioUrl] = useState(null); const audioRef = useRef(null); useEffect(() => { let objectUrl: string | null = null; let cancelled = false; async function fetchAudio() { setStatus("loading"); try { const res = await fetch("/api/synthesize", { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ text }), }); if (cancelled) return; if (!res.ok) { setStatus("idle"); return; } const blob = await res.blob(); if (cancelled) return; objectUrl = URL.createObjectURL(blob); setAudioUrl(objectUrl); setStatus("idle"); } catch { if (!cancelled) { setStatus("idle"); } } } fetchAudio(); return () => { cancelled = true; if (objectUrl) { URL.revokeObjectURL(objectUrl); } }; }, [text]); useEffect(() => { if (autoPlay && audioUrl && audioRef.current) { audioRef.current.play().catch(() => { // Browser may block autoplay; fall back to idle state setStatus("idle"); }); } }, [autoPlay, audioUrl]); function handlePlay() { if (audioRef.current) { audioRef.current.play(); } } function handlePause() { if (audioRef.current) { audioRef.current.pause(); } } function handleAudioEnded() { setStatus("idle"); if (audioUrl) { URL.revokeObjectURL(audioUrl); setAudioUrl(null); } } if (status === "loading") { return ( Loading audio... ); } return ( {status === "playing" ? ( ) : ( )} ); }