import React, { useRef, useEffect } from 'react'; import { usePlayerStore } from '@/stores/player'; import { Button } from '@/components/ui/button'; import { Slider } from '@/components/ui/slider'; import { Play, Pause, SkipBack, SkipForward, Volume2, VolumeX, Repeat, Shuffle, List, } from 'lucide-react'; import { useToast } from '@/hooks/use-toast'; import { QueuePanel } from './QueuePanel'; import { useState } from 'react'; export function AudioPlayer() { const audioRef = useRef(null); const { currentTrack, isPlaying, currentTime, duration, volume, muted, repeat, shuffle, setCurrentTime, setDuration, pause, resume, next, previous, setVolume, toggleMute, toggleShuffle, setRepeat, } = usePlayerStore(); const { toast } = useToast(); const [showQueue, setShowQueue] = useState(false); useEffect(() => { const audio = audioRef.current; if (!audio) return; const handleTimeUpdate = () => { setCurrentTime(audio.currentTime); }; const handleLoadedMetadata = () => { setDuration(audio.duration); }; const handleEnded = () => { if (repeat === 'track') { audio.currentTime = 0; audio.play(); } else { next(); } }; const handleError = () => { toast({ title: 'Playback error', description: 'Failed to play track', variant: 'destructive', }); }; audio.addEventListener('timeupdate', handleTimeUpdate); audio.addEventListener('loadedmetadata', handleLoadedMetadata); audio.addEventListener('ended', handleEnded); audio.addEventListener('error', handleError); return () => { audio.removeEventListener('timeupdate', handleTimeUpdate); audio.removeEventListener('loadedmetadata', handleLoadedMetadata); audio.removeEventListener('ended', handleEnded); audio.removeEventListener('error', handleError); }; }, [setCurrentTime, setDuration, next, repeat, toast]); useEffect(() => { const audio = audioRef.current; if (!audio) return; audio.volume = muted ? 0 : volume / 100; }, [volume, muted]); useEffect(() => { const audio = audioRef.current; if (!audio || !currentTrack) return; // Set audio source audio.src = currentTrack.url || ''; if (isPlaying) { audio.play().catch((err) => { console.error('Playback error:', err); pause(); }); } else { audio.pause(); } }, [currentTrack, isPlaying, pause]); const handlePlayPause = () => { if (isPlaying) { pause(); } else { resume(); } }; const handleSeek = (value: number[]) => { const audio = audioRef.current; if (audio) { audio.currentTime = value[0]; setCurrentTime(value[0]); } }; const handleVolumeChange = (value: number[]) => { setVolume(value[0]); }; const handleRepeatCycle = () => { const modes: Array<'off' | 'track' | 'playlist'> = [ 'off', 'track', 'playlist', ]; const currentIndex = modes.indexOf(repeat); setRepeat(modes[(currentIndex + 1) % modes.length]); }; // Keyboard shortcuts useEffect(() => { const handleKeyPress = (e: KeyboardEvent) => { // Space: play/pause if (e.code === 'Space' && !e.repeat) { e.preventDefault(); handlePlayPause(); } // Arrow left: seek backward if (e.code === 'ArrowLeft') { e.preventDefault(); const audio = audioRef.current; if (audio) { audio.currentTime = Math.max(0, audio.currentTime - 10); } } // Arrow right: seek forward if (e.code === 'ArrowRight') { e.preventDefault(); const audio = audioRef.current; if (audio) { audio.currentTime = Math.min(audio.duration, audio.currentTime + 10); } } }; window.addEventListener('keydown', handleKeyPress); return () => window.removeEventListener('keydown', handleKeyPress); }, [isPlaying]); const formatTime = (seconds: number) => { if (!isFinite(seconds)) return '0:00'; const mins = Math.floor(seconds / 60); const secs = Math.floor(seconds % 60); return `${mins}:${secs.toString().padStart(2, '0')}`; }; if (!currentTrack) { return null; } return ( <>