"use client"; import { SkipForward, Volume2, VolumeX } from "lucide-react"; import { useCallback, useEffect, useRef, useState } from "react"; import { Button } from "@/components/ui/button"; import { Label } from "@/components/ui/label"; import { AVATAR_AMBIENT_PLAYLIST, getAvatarAmbientTrackSrc, getAvatarAudioPreferences, setAvatarAudioPreferences, } from "@/lib/avatar-audio"; import { cn } from "@/lib/utils"; type AvatarAmbientAudioProps = { className?: string; }; export function AvatarAmbientAudio({ className }: AvatarAmbientAudioProps) { const audioRef = useRef(null); const volumeRef = useRef(0.6); const mutedRef = useRef(false); const [volume, setVolume] = useState(0.6); const [muted, setMuted] = useState(false); const [trackIndex, setTrackIndex] = useState(0); const [autoplayBlocked, setAutoplayBlocked] = useState(false); const [loadError, setLoadError] = useState(false); const currentTrack = AVATAR_AMBIENT_PLAYLIST[trackIndex]; const effectiveVolume = muted ? 0 : volume; useEffect(() => { volumeRef.current = volume; }, [volume]); useEffect(() => { mutedRef.current = muted; }, [muted]); useEffect(() => { const preferences = getAvatarAudioPreferences(); setVolume(preferences.volume); setMuted(preferences.muted); volumeRef.current = preferences.volume; mutedRef.current = preferences.muted; }, []); const persistPreferences = useCallback( (nextVolume: number, nextMuted: boolean) => { setAvatarAudioPreferences({ volume: nextVolume, muted: nextMuted }); }, [], ); const applyVolume = useCallback(async (nextVolume: number) => { const audio = audioRef.current; if (!audio) { return; } audio.volume = nextVolume; if (nextVolume === 0) { audio.pause(); return; } if (audio.readyState < HTMLMediaElement.HAVE_CURRENT_DATA) { return; } try { await audio.play(); setAutoplayBlocked(false); } catch { setAutoplayBlocked(true); } }, []); const loadTrack = useCallback( (index: number) => { const audio = audioRef.current; const track = AVATAR_AMBIENT_PLAYLIST[index]; if (!audio || !track) { return; } setLoadError(false); audio.src = getAvatarAmbientTrackSrc(track.filename); const handleCanPlay = () => { const nextVolume = mutedRef.current ? 0 : volumeRef.current; void applyVolume(nextVolume); }; const handleError = () => { setLoadError(true); }; audio.addEventListener("canplay", handleCanPlay, { once: true }); audio.addEventListener("error", handleError, { once: true }); audio.load(); }, [applyVolume], ); useEffect(() => { loadTrack(trackIndex); }, [loadTrack, trackIndex]); useEffect(() => { void applyVolume(effectiveVolume); }, [applyVolume, effectiveVolume]); useEffect(() => { persistPreferences(volume, muted); }, [volume, muted, persistPreferences]); useEffect(() => { const audio = audioRef.current; if (!audio) { return; } const handleEnded = () => { setTrackIndex((current) => (current + 1) % AVATAR_AMBIENT_PLAYLIST.length); }; audio.addEventListener("ended", handleEnded); return () => { audio.removeEventListener("ended", handleEnded); audio.pause(); }; }, []); const handleToggleMute = () => { setMuted((current) => !current); }; const handleVolumeChange = (nextVolume: number) => { setVolume(nextVolume); if (nextVolume > 0 && muted) { setMuted(false); } if (nextVolume === 0) { setMuted(true); } }; const handleSkip = () => { setTrackIndex((current) => (current + 1) % AVATAR_AMBIENT_PLAYLIST.length); }; const handleUserActivate = () => { void applyVolume(effectiveVolume); }; return (
); }