import React, { useState, useRef, useEffect } from 'react'; import { Box, HStack, VStack, Text, IconButton, Slider, SliderTrack, SliderFilledTrack, SliderThumb, Icon, useToast, } from '@chakra-ui/react'; import { FiPlay, FiPause, FiSkipBack, FiSkipForward, FiVolume2, FiVolumeX, FiX, } from 'react-icons/fi'; import type { Song } from '../types/interfaces'; interface PersistentMusicPlayerProps { currentSong: Song | null; onClose: () => void; } export const PersistentMusicPlayer: React.FC = ({ currentSong, onClose, }) => { const [isPlaying, setIsPlaying] = useState(false); const [currentTime, setCurrentTime] = useState(0); const [duration, setDuration] = useState(0); const [volume, setVolume] = useState(1); const [isMuted, setIsMuted] = useState(false); const [streamingUrl, setStreamingUrl] = useState(null); const [isLoading, setIsLoading] = useState(false); const audioRef = useRef(null); const toast = useToast(); // Format time in MM:SS const formatTime = (seconds: number): string => { if (!seconds || isNaN(seconds)) return '00:00'; const mins = Math.floor(seconds / 60); const secs = Math.floor(seconds % 60); return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; }; // Load streaming URL when current song changes useEffect(() => { if (currentSong && currentSong.s3File?.hasS3File) { loadStreamingUrl(); } else { setStreamingUrl(null); setIsPlaying(false); } }, [currentSong]); const loadStreamingUrl = async () => { if (!currentSong?.s3File?.musicFileId) return; // Handle both string ID and populated object const musicFileId = typeof currentSong.s3File.musicFileId === 'string' ? currentSong.s3File.musicFileId : currentSong.s3File.musicFileId._id; if (!musicFileId) return; setIsLoading(true); try { const response = await fetch(`/api/music/${musicFileId}/stream`); if (response.ok) { const data = await response.json(); setStreamingUrl(data.streamingUrl); // Auto-play when URL is loaded setTimeout(() => { if (audioRef.current) { audioRef.current.play().then(() => { setIsPlaying(true); // Prevent the audio element from stealing focus audioRef.current?.blur(); }).catch(error => { console.error('Error auto-playing:', error); }); } }, 100); } else { throw new Error('Failed to get streaming URL'); } } catch (error) { console.error('Error loading streaming URL:', error); toast({ title: 'Error', description: 'Failed to load music file', status: 'error', duration: 3000, isClosable: true, }); } finally { setIsLoading(false); } }; const handlePlay = () => { if (audioRef.current) { audioRef.current.play().then(() => { setIsPlaying(true); }).catch(error => { console.error('Error playing:', error); }); } }; const handlePause = () => { if (audioRef.current) { audioRef.current.pause(); setIsPlaying(false); } }; const handleTimeUpdate = () => { if (audioRef.current) { setCurrentTime(audioRef.current.currentTime); } }; const handleLoadedMetadata = () => { if (audioRef.current) { setDuration(audioRef.current.duration); } }; const handleEnded = () => { setIsPlaying(false); setCurrentTime(0); }; const handleSeek = (value: number) => { if (audioRef.current) { audioRef.current.currentTime = value; setCurrentTime(value); } }; const handleVolumeChange = (value: number) => { setVolume(value); if (audioRef.current) { audioRef.current.volume = value; } if (value === 0) { setIsMuted(true); } else if (isMuted) { setIsMuted(false); } }; const toggleMute = () => { if (audioRef.current) { if (isMuted) { audioRef.current.volume = volume; setIsMuted(false); } else { audioRef.current.volume = 0; setIsMuted(true); } } }; const skipBackward = () => { if (audioRef.current) { audioRef.current.currentTime = Math.max(0, audioRef.current.currentTime - 10); } }; const skipForward = () => { if (audioRef.current) { audioRef.current.currentTime = Math.min( audioRef.current.duration, audioRef.current.currentTime + 10 ); } }; if (!currentSong || !currentSong.s3File?.hasS3File) { return null; } return ( {/* Hidden audio element */} ); };