import React, { createContext, useContext, useState, useEffect, useRef, } from 'react'; import { Track } from '../types'; export interface VisualizerSettings { mode: 'waveform' | 'spectrogram' | 'bars' | 'off'; color: string; sensitivity: number; } interface AudioContextType { currentTrack: Track | null; isPlaying: boolean; queue: Track[]; history: Track[]; progress: number; // 0-100 currentTime: number; duration: number; volume: number; isMuted: boolean; shuffle: boolean; repeatMode: 'off' | 'all' | 'one'; playbackRate: number; pitchCorrection: boolean; visualizerSettings: VisualizerSettings; autoplay: boolean; // Actions playTrack: (track: Track, context?: Track[]) => void; togglePlay: () => void; nextTrack: () => void; prevTrack: () => void; seek: (percent: number) => void; setVolume: (val: number) => void; toggleMute: () => void; toggleShuffle: () => void; toggleRepeat: () => void; setPlaybackRate: (rate: number) => void; togglePitchCorrection: () => void; setVisualizerSettings: (settings: VisualizerSettings) => void; addToQueue: (track: Track) => void; removeFromQueue: (trackId: string) => void; playNext: (track: Track) => void; reorderQueue: (fromIndex: number, toIndex: number) => void; clearQueue: () => void; toggleAutoplay: () => void; } const AudioContext = createContext(undefined); export const useAudio = () => { const context = useContext(AudioContext); if (!context) throw new Error('useAudio must be used within AudioProvider'); return context; }; // Mock Data for Initial State const mockTracks: Track[] = [ { id: '1', title: 'Neon Nightrider', artist: 'Cyber_Punk_OST', album: 'Night City Vol.1', duration: '3:45', durationSec: 225, plays: 12000, like_count: 3400, coverUrl: 'https://picsum.photos/id/55/400/400', isPremium: true, waveformData: Array.from({ length: 100 }, () => Math.random()), lyrics: [ { time: 10, text: 'Neon lights flickering...' }, { time: 15, text: 'Driving through the cyber city' }, { time: 20, text: 'Bass dropping heavy on the pavement' }, ], }, { id: '2', title: 'Glitch in the Matrix', artist: 'Null Pointer', album: 'System Failure', duration: '4:20', durationSec: 260, plays: 8500, like_count: 2100, coverUrl: 'https://picsum.photos/id/58/400/400', waveformData: Array.from({ length: 100 }, () => Math.random()), }, { id: '3', title: 'Tokyo Drift (Lofi)', artist: 'Sakura Beats', album: 'Chillhop Essentials', duration: '2:55', durationSec: 175, plays: 45000, like_count: 12000, coverUrl: 'https://picsum.photos/id/60/400/400', isPremium: true, waveformData: Array.from({ length: 100 }, () => Math.random()), }, { id: '4', title: 'Neural Link', artist: 'Mainframe', album: 'AI Dreams', duration: '5:10', durationSec: 310, plays: 2300, like_count: 450, coverUrl: 'https://picsum.photos/id/70/200/200', waveformData: Array.from({ length: 100 }, () => Math.random()), }, { id: '5', title: 'Synthwave Sunset', artist: 'Retro Boy', album: 'Analog Memories', duration: '3:30', durationSec: 210, plays: 1200, like_count: 300, coverUrl: 'https://picsum.photos/id/80/200/200', waveformData: Array.from({ length: 100 }, () => Math.random()), }, ] as unknown as Track[]; export const AudioProvider: React.FC<{ children: React.ReactNode }> = ({ children, }) => { const [currentTrack, setCurrentTrack] = useState(mockTracks[0]); const [queue, setQueue] = useState(mockTracks.slice(1)); const [history, setHistory] = useState([]); const [isPlaying, setIsPlaying] = useState(false); const [progress, setProgress] = useState(0); const [currentTime, setCurrentTime] = useState(0); const [volume, setVolumeState] = useState(80); const [isMuted, _setIsMuted] = useState(false); const [shuffle, setShuffle] = useState(false); const [repeatMode, setRepeatMode] = useState<'off' | 'all' | 'one'>('off'); // Phase 8 & 9 New States const [playbackRate, setPlaybackRate] = useState(1.0); const [pitchCorrection, setPitchCorrection] = useState(true); const [visualizerSettings, setVisualizerSettings] = useState({ mode: 'waveform', color: '#66FCF1', sensitivity: 50, }); const [autoplay, setAutoplay] = useState(true); const audioInterval = useRef(null); // Simulation of Audio Playback useEffect(() => { if (isPlaying && currentTrack) { audioInterval.current = window.setInterval(() => { setCurrentTime((prev) => { if (prev >= currentTrack.durationSec) { // Track finished if (repeatMode === 'one') { return 0; // Restart } if (queue.length > 0 || autoplay) { nextTrack(); // Auto next logic handled there } else { setIsPlaying(false); return prev; } return 0; } // Adjust increment based on playback rate return prev + 1 * playbackRate; }); }, 1000 / playbackRate); // Interval adjusts to speed } else { if (audioInterval.current) clearInterval(audioInterval.current); } return () => { if (audioInterval.current) clearInterval(audioInterval.current); }; }, [isPlaying, currentTrack, repeatMode, playbackRate, queue, autoplay]); // Sync Progress Percentage useEffect(() => { if (currentTrack) { setProgress((currentTime / currentTrack.durationSec) * 100); } }, [currentTime, currentTrack]); const playTrack = (track: Track, context?: Track[]) => { if (currentTrack && currentTrack.id !== track.id) { setHistory((prev) => [...prev, currentTrack]); } setCurrentTrack(track); if (context) { const trackIndex = context.findIndex((t) => t.id === track.id); if (trackIndex !== -1) { setQueue(context.slice(trackIndex + 1)); } } setIsPlaying(true); setCurrentTime(0); }; const togglePlay = () => setIsPlaying(!isPlaying); const nextTrack = () => { if (queue.length > 0) { const next = shuffle ? queue[Math.floor(Math.random() * queue.length)] : queue[0]; setHistory((prev) => (currentTrack ? [...prev, currentTrack] : prev)); if (repeatMode !== 'all') { setQueue((prev) => prev.filter((t) => t.id !== next.id)); } else { setQueue((prev) => [...prev.filter((t) => t.id !== next.id), next]); } setCurrentTrack(next); setCurrentTime(0); setIsPlaying(true); } else if (autoplay) { // Mock Autoplay logic: Pick random track from "Network" // In real app, this fetches recommendation const randomMock = mockTracks[Math.floor(Math.random() * mockTracks.length)]; // Don't add to history if it's autoplay transition usually, but for mock simplicty: setHistory((prev) => (currentTrack ? [...prev, currentTrack] : prev)); setCurrentTrack({ ...randomMock, id: `auto-${Date.now()}`, title: `Autoplay: ${randomMock.title}`, }); setCurrentTime(0); setIsPlaying(true); } else { setIsPlaying(false); setCurrentTime(0); } }; const prevTrack = () => { if (currentTime > 3) { setCurrentTime(0); } else if (history.length > 0) { const prev = history[history.length - 1]; setQueue((prevQ) => (currentTrack ? [currentTrack, ...prevQ] : prevQ)); setHistory((prevH) => prevH.slice(0, -1)); setCurrentTrack(prev); setCurrentTime(0); setIsPlaying(true); } }; const seek = (percent: number) => { if (currentTrack) { const newTime = (percent / 100) * currentTrack.durationSec; setCurrentTime(newTime); setProgress(percent); } }; const setVolume = (val: number) => setVolumeState(val); const toggleMute = () => setIsPlaying((prev) => !prev); // Simplified mock const toggleShuffle = () => setShuffle(!shuffle); const toggleRepeat = () => { const modes: ('off' | 'all' | 'one')[] = ['off', 'all', 'one']; const next = modes[(modes.indexOf(repeatMode) + 1) % modes.length]; setRepeatMode(next); }; const togglePitchCorrection = () => setPitchCorrection(!pitchCorrection); const toggleAutoplay = () => setAutoplay(!autoplay); const addToQueue = (track: Track) => setQueue((prev) => [...prev, track]); const playNext = (track: Track) => setQueue((prev) => [track, ...prev]); const removeFromQueue = (id: string) => setQueue((prev) => prev.filter((t) => t.id !== id)); const clearQueue = () => setQueue([]); const reorderQueue = (fromIndex: number, toIndex: number) => { const result = Array.from(queue); const [removed] = result.splice(fromIndex, 1); result.splice(toIndex, 0, removed); setQueue(result); }; return ( {children} ); };