Files
sdb-front/src/contexts/MusicPlayerContext.tsx

319 lines
8.3 KiB
TypeScript

import { createContext, useContext, useState, useEffect, type ReactNode } from 'react'
import { useSocket } from './SocketContext'
import { apiService } from '@/services/api'
export interface Track {
id: string
title: string
artist?: string
duration: number
thumbnail?: string
url: string
}
export type PlayMode = 'continuous' | 'loop-playlist' | 'loop-one' | 'random' | 'single'
interface MusicPlayerContextType {
// Playback state
isPlaying: boolean
currentTime: number
duration: number
volume: number
isMuted: boolean
playMode: PlayMode
// Current track and playlist
currentTrack: Track | null
playlist: Track[]
currentTrackIndex: number
// UI state
isMinimized: boolean
isUltraMinimized: boolean
showPlaylist: boolean
// Actions
play: () => void
pause: () => void
stop: () => void
togglePlayPause: () => void
seekTo: (time: number) => void
setVolume: (volume: number) => void
toggleMute: () => void
setPlayMode: (mode: PlayMode) => void
nextTrack: () => void
previousTrack: () => void
playTrack: (trackIndex: number) => void
loadPlaylist: (playlistId: number) => void
addToPlaylist: (track: Track) => void
removeFromPlaylist: (trackId: string) => void
clearPlaylist: () => void
toggleMaximize: () => void
toggleUltraMinimize: () => void
togglePlaylistVisibility: () => void
}
const MusicPlayerContext = createContext<MusicPlayerContextType | undefined>(undefined)
export function useMusicPlayer() {
const context = useContext(MusicPlayerContext)
if (context === undefined) {
throw new Error('useMusicPlayer must be used within a MusicPlayerProvider')
}
return context
}
interface MusicPlayerProviderProps {
children: ReactNode
}
export function MusicPlayerProvider({ children }: MusicPlayerProviderProps) {
const { socket } = useSocket()
// Playback state
const [isPlaying, setIsPlaying] = useState(false)
const [currentTime, setCurrentTime] = useState(0)
const [duration, setDuration] = useState(0)
const [volume, setVolumeState] = useState(80)
const [isMuted, setIsMuted] = useState(false)
const [playMode, setPlayModeState] = useState<PlayMode>('continuous')
// Playlist state
const [currentTrackIndex, setCurrentTrackIndex] = useState(0)
const [playlist, setPlaylist] = useState<Track[]>([])
const [currentPlaylistId, setCurrentPlaylistId] = useState<number | null>(null)
const [currentTrack, setCurrentTrack] = useState<Track | null>(null)
// UI state
const [isMinimized, setIsMinimized] = useState(true)
const [isUltraMinimized, setIsUltraMinimized] = useState(false)
const [showPlaylist, setShowPlaylist] = useState(false)
// Fetch initial player state on mount
useEffect(() => {
const fetchInitialState = async () => {
try {
const response = await apiService.get('/api/player/state')
const state = await response.json()
// 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()
}, [])
// Listen for real-time player updates via SocketIO
useEffect(() => {
if (!socket) {
return;
}
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)
}
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 pause = async () => {
try {
await apiService.post('/api/player/pause')
} catch (error) {
console.error('Failed to pause:', error)
}
}
const stop = async () => {
try {
await apiService.post('/api/player/stop')
} catch (error) {
console.error('Failed to stop:', error)
}
}
const togglePlayPause = async () => {
if (isPlaying) {
await pause()
} else {
await play()
}
}
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 = async (newVolume: number) => {
try {
await apiService.post('/api/player/volume', { volume: newVolume })
} catch (error) {
console.error('Failed to set volume:', error)
}
}
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 nextTrack = async () => {
try {
await apiService.post('/api/player/next')
} catch (error) {
console.error('Failed to skip to next track:', error)
}
}
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) => {
// This would need to be implemented via API
console.log('Adding to playlist not yet implemented')
}
const removeFromPlaylist = (trackId: string) => {
// This would need to be implemented via API
console.log('Removing from playlist not yet implemented')
}
const clearPlaylist = () => {
// 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 = () => {
setIsMinimized(!isMinimized)
setIsUltraMinimized(false) // When maximizing, exit ultra-minimize mode
}
const toggleUltraMinimize = () => {
setIsUltraMinimized(!isUltraMinimized)
if (!isUltraMinimized) {
setIsMinimized(true) // When ultra-minimizing, ensure we're in minimized mode
}
}
const togglePlaylistVisibility = () => {
setShowPlaylist(!showPlaylist)
}
const value: MusicPlayerContextType = {
// Playback state
isPlaying,
currentTime,
duration,
volume,
isMuted,
playMode,
// Current track and playlist
currentTrack,
playlist,
currentTrackIndex,
// UI state
isMinimized,
isUltraMinimized,
showPlaylist,
// Actions
play,
pause,
stop,
togglePlayPause,
seekTo,
setVolume,
toggleMute,
setPlayMode,
nextTrack,
previousTrack,
playTrack,
loadPlaylist,
addToPlaylist,
removeFromPlaylist,
clearPlaylist,
toggleMaximize,
toggleUltraMinimize,
togglePlaylistVisibility,
}
return (
<MusicPlayerContext.Provider value={value}>
{children}
</MusicPlayerContext.Provider>
)
}