diff --git a/src/components/AuthProvider.tsx b/src/components/AuthProvider.tsx index fd28c07..0daabbe 100644 --- a/src/components/AuthProvider.tsx +++ b/src/components/AuthProvider.tsx @@ -23,7 +23,6 @@ export function AuthProvider({ children }: { children: ReactNode }) { // Listen for refresh token expiration events const handleRefreshTokenExpired = () => { - console.log('Refresh token expired, logging out user') setUser(null) } diff --git a/src/components/MusicPlayer.tsx b/src/components/MusicPlayer.tsx index b0fb9c8..70cda77 100644 --- a/src/components/MusicPlayer.tsx +++ b/src/components/MusicPlayer.tsx @@ -47,7 +47,8 @@ export function MusicPlayer() { const progressBarRef = useRef(null) - if (!currentTrack) { + // Show player if there's a playlist, even if no current track is playing + if (playlist.length === 0) { return null } @@ -98,7 +99,7 @@ export function MusicPlayer() { return ( {/* Thumbnail */} - {currentTrack.thumbnail && ( + {currentTrack?.thumbnail && (

- {currentTrack.title} + {currentTrack?.title || 'No track selected'}

- {currentTrack.artist && ( + {currentTrack?.artist && (

{currentTrack.artist}

@@ -262,7 +263,7 @@ export function MusicPlayer() { {/* Main player area */}
{/* Large thumbnail */} - {currentTrack.thumbnail && ( + {currentTrack?.thumbnail && (
-

{currentTrack.title}

- {currentTrack.artist && ( +

{currentTrack?.title || 'No track selected'}

+ {currentTrack?.artist && (

{currentTrack.artist}

)}
diff --git a/src/contexts/MusicPlayerContext.tsx b/src/contexts/MusicPlayerContext.tsx index 34ba42f..713595e 100644 --- a/src/contexts/MusicPlayerContext.tsx +++ b/src/contexts/MusicPlayerContext.tsx @@ -1,4 +1,6 @@ import { createContext, useContext, useState, useRef, useEffect, type ReactNode } from 'react' +import { useSocket } from './SocketContext' +import { apiService } from '@/services/api' export interface Track { id: string @@ -41,6 +43,7 @@ interface MusicPlayerContextType { nextTrack: () => void previousTrack: () => void playTrack: (trackIndex: number) => void + loadPlaylist: (playlistId: number) => void addToPlaylist: (track: Track) => void removeFromPlaylist: (trackId: string) => void clearPlaylist: () => void @@ -63,232 +66,202 @@ interface MusicPlayerProviderProps { } export function MusicPlayerProvider({ children }: MusicPlayerProviderProps) { - const audioRef = useRef(null) + const { socket } = useSocket() // Playback state const [isPlaying, setIsPlaying] = useState(false) const [currentTime, setCurrentTime] = useState(0) const [duration, setDuration] = useState(0) - const [volume, setVolumeState] = useState(0.8) + const [volume, setVolumeState] = useState(80) const [isMuted, setIsMuted] = useState(false) - const [playMode, setPlayMode] = useState('continuous') + const [playMode, setPlayModeState] = useState('continuous') // Playlist state const [currentTrackIndex, setCurrentTrackIndex] = useState(0) - const [playlist, setPlaylist] = useState([ - { - id: '1', - title: 'Cheryl Lynn - Got To Be Real', - artist: 'Cheryl Lynn', - duration: 240, - thumbnail: '/api/sounds/stream/thumbnails/Cheryl Lynn - Got To Be Real Official Audio_fI569nw0YUQ.webp', - url: '/api/sounds/stream/Cheryl Lynn - Got To Be Real Official Audio_fI569nw0YUQ.opus' - }, - { - id: '2', - title: 'The Whispers - And The Beat Goes On', - artist: 'The Whispers', - duration: 280, - thumbnail: '/api/sounds/stream/thumbnails/The Whispers - And The Beat Goes On Official Video_pEmX5HR9ZxU.jpg', - url: '/api/sounds/stream/The Whispers - And The Beat Goes On Official Video_pEmX5HR9ZxU.opus' - }, - { - id: '3', - title: 'OLD RAP VS NEW RAP', - artist: 'Mister V, Jhon Rachid, Maskey', - duration: 320, - thumbnail: '/api/sounds/stream/thumbnails/OLD RAP VS NEW RAP Ft Mister V Jhon Rachid Maskey _PAFYcOFE3DY.webp', - url: '/api/sounds/stream/OLD RAP VS NEW RAP Ft Mister V Jhon Rachid Maskey _PAFYcOFE3DY.opus' - } - ]) + const [playlist, setPlaylist] = useState([]) + const [currentPlaylistId, setCurrentPlaylistId] = useState(null) + const [currentTrack, setCurrentTrack] = useState(null) // UI state const [isMinimized, setIsMinimized] = useState(true) const [showPlaylist, setShowPlaylist] = useState(false) - - const currentTrack = playlist[currentTrackIndex] || null - // Initialize audio element + // Fetch initial player state on mount useEffect(() => { - audioRef.current = new Audio() - - const audio = audioRef.current - - const handleTimeUpdate = () => { - setCurrentTime(audio.currentTime) - } - - const handleLoadedMetadata = () => { - setDuration(audio.duration) - } - - const handleEnded = () => { - handleTrackEnd() - } - - audio.addEventListener('timeupdate', handleTimeUpdate) - audio.addEventListener('loadedmetadata', handleLoadedMetadata) - audio.addEventListener('ended', handleEnded) - - return () => { - audio.removeEventListener('timeupdate', handleTimeUpdate) - audio.removeEventListener('loadedmetadata', handleLoadedMetadata) - audio.removeEventListener('ended', handleEnded) - audio.pause() + const fetchInitialState = async () => { + try { + const response = await apiService.get('/api/player/state') + let state = await response.json() + + // If no playlist is loaded, try to load the main playlist + if (!state.playlist_id) { + try { + await apiService.post('/api/player/load-main-playlist') + // Fetch state again after loading main playlist + const newResponse = await apiService.get('/api/player/state') + state = await newResponse.json() + } catch (loadError) { + console.warn('Failed to load main playlist:', loadError) + } + } + + // Update all state from backend + setIsPlaying(state.is_playing || false) + setCurrentTime(state.current_time || 0) + setDuration(state.duration || 0) + setVolumeState(state.volume || 80) + setIsMuted(state.volume === 0) + setPlayModeState(state.play_mode || 'continuous') + setCurrentTrackIndex(state.current_track_index || 0) + setPlaylist(state.playlist || []) + setCurrentPlaylistId(state.playlist_id || null) + setCurrentTrack(state.current_track || null) + + } catch (error) { + console.error('Failed to fetch initial player state:', error) + } } + + fetchInitialState() }, []) - // Update audio source when track changes + // Listen for real-time player updates via SocketIO useEffect(() => { - if (audioRef.current && currentTrack) { - audioRef.current.src = currentTrack.url - audioRef.current.load() + if (!socket) { + return; } - }, [currentTrack]) - // Update audio volume and mute state - useEffect(() => { - if (audioRef.current) { - audioRef.current.volume = isMuted ? 0 : volume + const handlePlayerStateUpdate = (state: any) => { + setIsPlaying(state.is_playing || false) + setCurrentTime(state.current_time || 0) + setDuration(state.duration || 0) + setVolumeState(state.volume || 80) + setIsMuted(state.volume === 0) + setPlayModeState(state.play_mode || 'continuous') + setCurrentTrackIndex(state.current_track_index || 0) + setPlaylist(state.playlist || []) + setCurrentPlaylistId(state.playlist_id || null) + setCurrentTrack(state.current_track || null) } - }, [volume, isMuted]) - const handleTrackEnd = () => { - switch (playMode) { - case 'loop-one': - if (audioRef.current) { - audioRef.current.currentTime = 0 - audioRef.current.play() - } - break - case 'loop-playlist': - nextTrack() - break - case 'random': - playRandomTrack() - break - case 'continuous': - if (currentTrackIndex < playlist.length - 1) { - nextTrack() - } else { - stop() - } - break + socket.on('player_state_update', handlePlayerStateUpdate) + + return () => { + socket.off('player_state_update', handlePlayerStateUpdate) + } + }, [socket]) + + const play = async () => { + try { + await apiService.post('/api/player/play') + } catch (error) { + console.error('Failed to play:', error) } } - const play = () => { - if (audioRef.current && currentTrack) { - audioRef.current.play() - setIsPlaying(true) + const pause = async () => { + try { + await apiService.post('/api/player/pause') + } catch (error) { + console.error('Failed to pause:', error) } } - const pause = () => { - if (audioRef.current) { - audioRef.current.pause() - setIsPlaying(false) + const stop = async () => { + try { + await apiService.post('/api/player/stop') + } catch (error) { + console.error('Failed to stop:', error) } } - const stop = () => { - if (audioRef.current) { - audioRef.current.pause() - audioRef.current.currentTime = 0 - setIsPlaying(false) - setCurrentTime(0) - } - } - - const togglePlayPause = () => { + const togglePlayPause = async () => { if (isPlaying) { - pause() + await pause() } else { - play() + await play() } } - const seekTo = (time: number) => { - if (audioRef.current) { - audioRef.current.currentTime = time - setCurrentTime(time) + const seekTo = async (time: number) => { + try { + const position = duration > 0 ? time / duration : 0 + await apiService.post('/api/player/seek', { position }) + } catch (error) { + console.error('Failed to seek:', error) } } - const setVolume = (newVolume: number) => { - setVolumeState(newVolume) - setIsMuted(newVolume === 0) - } - - const toggleMute = () => { - setIsMuted(!isMuted) - } - - const nextTrack = () => { - if (playlist.length === 0) return - - const nextIndex = (currentTrackIndex + 1) % playlist.length - setCurrentTrackIndex(nextIndex) - - // Auto-play if currently playing - if (isPlaying) { - setTimeout(() => play(), 100) + const setVolume = async (newVolume: number) => { + try { + await apiService.post('/api/player/volume', { volume: newVolume }) + } catch (error) { + console.error('Failed to set volume:', error) } } - const previousTrack = () => { - if (playlist.length === 0) return - - const prevIndex = currentTrackIndex === 0 ? playlist.length - 1 : currentTrackIndex - 1 - setCurrentTrackIndex(prevIndex) - - // Auto-play if currently playing - if (isPlaying) { - setTimeout(() => play(), 100) + const toggleMute = async () => { + try { + const newVolume = isMuted ? 80 : 0 + await apiService.post('/api/player/volume', { volume: newVolume }) + } catch (error) { + console.error('Failed to toggle mute:', error) } } - const playRandomTrack = () => { - if (playlist.length <= 1) return - - let randomIndex - do { - randomIndex = Math.floor(Math.random() * playlist.length) - } while (randomIndex === currentTrackIndex) - - setCurrentTrackIndex(randomIndex) - setTimeout(() => play(), 100) + const nextTrack = async () => { + try { + await apiService.post('/api/player/next') + } catch (error) { + console.error('Failed to skip to next track:', error) + } } - const playTrack = (trackIndex: number) => { - if (trackIndex >= 0 && trackIndex < playlist.length) { - setCurrentTrackIndex(trackIndex) - setTimeout(() => play(), 100) + const previousTrack = async () => { + try { + await apiService.post('/api/player/previous') + } catch (error) { + console.error('Failed to skip to previous track:', error) + } + } + + const playTrack = async (trackIndex: number) => { + try { + await apiService.post('/api/player/play-track', { index: trackIndex }) + } catch (error) { + console.error('Failed to play track:', error) + } + } + + const loadPlaylist = async (playlistId: number) => { + try { + await apiService.post('/api/player/playlist', { playlist_id: playlistId }) + } catch (error) { + console.error('Failed to load playlist:', error) } } const addToPlaylist = (track: Track) => { - setPlaylist(prev => [...prev, track]) + // This would need to be implemented via API + console.log('Adding to playlist not yet implemented') } const removeFromPlaylist = (trackId: string) => { - setPlaylist(prev => { - const newPlaylist = prev.filter(track => track.id !== trackId) - // Adjust current track index if necessary - const removedIndex = prev.findIndex(track => track.id === trackId) - if (removedIndex !== -1 && removedIndex < currentTrackIndex) { - setCurrentTrackIndex(current => Math.max(0, current - 1)) - } else if (removedIndex === currentTrackIndex && newPlaylist.length > 0) { - setCurrentTrackIndex(current => Math.min(current, newPlaylist.length - 1)) - } - return newPlaylist - }) + // This would need to be implemented via API + console.log('Removing from playlist not yet implemented') } const clearPlaylist = () => { - stop() - setPlaylist([]) - setCurrentTrackIndex(0) + // This would need to be implemented via API + console.log('Clearing playlist not yet implemented') + } + + const setPlayMode = async (mode: PlayMode) => { + try { + await apiService.post('/api/player/mode', { mode }) + } catch (error) { + console.error('Failed to set play mode:', error) + } } const toggleMaximize = () => { @@ -303,7 +276,7 @@ export function MusicPlayerProvider({ children }: MusicPlayerProviderProps) { // Playback state isPlaying, currentTime, - duration: currentTrack?.duration || 0, + duration, volume, isMuted, playMode, @@ -329,6 +302,7 @@ export function MusicPlayerProvider({ children }: MusicPlayerProviderProps) { nextTrack, previousTrack, playTrack, + loadPlaylist, addToPlaylist, removeFromPlaylist, clearPlaylist, diff --git a/src/contexts/SocketContext.tsx b/src/contexts/SocketContext.tsx index c4fbab5..7240f19 100644 --- a/src/contexts/SocketContext.tsx +++ b/src/contexts/SocketContext.tsx @@ -44,6 +44,7 @@ export const SocketProvider: React.FC = ({ children }) => { // Set up event listeners newSocket.on("connect", () => { + // Send authentication after connection newSocket.emit("authenticate", {}); }); @@ -75,7 +76,9 @@ export const SocketProvider: React.FC = ({ children }) => { // Connect/disconnect based on authentication state useEffect(() => { - if (!socket || loading) return; + if (!socket || loading) { + return; + } if (user && !isConnected) { socket.connect(); diff --git a/src/services/api.ts b/src/services/api.ts index 8b584bd..57b01e4 100644 --- a/src/services/api.ts +++ b/src/services/api.ts @@ -82,11 +82,8 @@ class ApiService { }) if (response.ok) { - console.log('Token refreshed successfully') return true } else { - console.log('Token refresh failed:', response.status) - // If refresh token is also expired (401), trigger logout if (response.status === 401) { this.handleLogout() @@ -103,9 +100,6 @@ class ApiService { * Handle logout when refresh token expires */ private handleLogout() { - // Clear any local storage or state if needed - console.log('Refresh token expired, user needs to login again') - // Dispatch a custom event that the AuthContext can listen to window.dispatchEvent(new CustomEvent('auth:refresh-token-expired')) }