1- import React , { useState } from 'react' ;
1+ import React , { useState , useEffect } from 'react' ;
22import { Link } from 'react-router-dom' ;
33import CloudPlaylist from './components/CloudPlaylist' ;
44import './CloudMusicPlayer.css' ;
88 PlusIcon , LinkIcon , PlayIcon , PauseIcon ,
99 SkipForwardIcon , SkipBackIcon , RepeatIcon ,
1010 ShuffleIcon , SpeakerHighIcon , SpeakerSlashIcon , SpeakerLowIcon , SpeakerNoneIcon ,
11- WaveformIcon , ArrowLeftIcon
11+ WaveformIcon , ArrowLeftIcon , QuotesIcon
1212} from '@phosphor-icons/react' ;
1313import CustomSlider from '../../../components/CustomSlider' ;
1414import GenerativeArt from '../../../components/GenerativeArt' ;
@@ -28,6 +28,9 @@ const CloudMusicPlayer = () => {
2828 const [ newTitle , setNewTitle ] = useState ( '' ) ;
2929 const [ newArtist , setNewArtist ] = useState ( '' ) ;
3030 const [ showVisualizer , setShowVisualizer ] = useState ( true ) ;
31+ const [ showLyrics , setShowLyrics ] = useState ( false ) ;
32+ const [ lyrics , setLyrics ] = useState ( '' ) ;
33+ const [ loadingLyrics , setLoadingLyrics ] = useState ( false ) ;
3134
3235 // Format time (mm:ss)
3336 const formatTime = ( time ) => {
@@ -37,6 +40,33 @@ const CloudMusicPlayer = () => {
3740 return `${ minutes . toString ( ) . padStart ( 2 , '0' ) } :${ seconds . toString ( ) . padStart ( 2 , '0' ) } ` ;
3841 } ;
3942
43+ useEffect ( ( ) => {
44+ const fetchLyrics = async ( ) => {
45+ if ( ! currentTrack ?. title || ! currentTrack ?. artist ) {
46+ setLyrics ( '' ) ;
47+ return ;
48+ }
49+ setLoadingLyrics ( true ) ;
50+ try {
51+ const response = await fetch ( `https://lrclib.net/api/get?artist_name=${ encodeURIComponent ( currentTrack . artist ) } &track_name=${ encodeURIComponent ( currentTrack . title ) } ` ) ;
52+ if ( response . ok ) {
53+ const data = await response . json ( ) ;
54+ setLyrics ( data . plainLyrics || data . syncedLyrics ?. replace ( / \[ \d + : \d + .\d + \] / g, '' ) || "LYRICS_NOT_FOUND" ) ;
55+ } else {
56+ setLyrics ( "LYRICS_NOT_FOUND" ) ;
57+ }
58+ } catch ( err ) {
59+ setLyrics ( "ERROR_FETCHING_DATA" ) ;
60+ } finally {
61+ setLoadingLyrics ( false ) ;
62+ }
63+ } ;
64+
65+ if ( showLyrics ) {
66+ fetchLyrics ( ) ;
67+ }
68+ } , [ currentTrack , showLyrics ] ) ;
69+
4070 const handleAddTrack = ( e ) => {
4171 e . preventDefault ( ) ;
4272 if ( ! newUrl || ! newTitle ) return ;
@@ -187,7 +217,14 @@ const CloudMusicPlayer = () => {
187217 ) }
188218
189219 { /* Status Overlay */ }
190- < div className = "absolute top-4 right-4 flex gap-2 z-20" >
220+ < div className = "absolute top-4 right-4 flex gap-2 z-30" >
221+ < button
222+ onClick = { ( ) => setShowLyrics ( ! showLyrics ) }
223+ className = { `bg-black/80 border border-cyan-500 px-2 py-1 text-xs font-bold transition-colors ${ showLyrics ? 'text-cyan-400 shadow-[0_0_10px_rgba(0,255,255,0.3)]' : 'text-cyan-800' } ` }
224+ title = "Toggle Lyrics"
225+ >
226+ < QuotesIcon size = { 16 } weight = { showLyrics ? "fill" : "regular" } />
227+ </ button >
191228 < button
192229 onClick = { ( ) => setShowVisualizer ( ! showVisualizer ) }
193230 className = { `bg-black/80 border border-cyan-500 px-2 py-1 text-xs font-bold transition-colors ${ showVisualizer ? 'text-cyan-400 shadow-[0_0_10px_rgba(0,255,255,0.3)]' : 'text-cyan-800' } ` }
@@ -199,6 +236,36 @@ const CloudMusicPlayer = () => {
199236 { isPlaying ? 'PLAYING' : 'PAUSED' }
200237 </ div >
201238 </ div >
239+
240+ { /* Lyrics Display */ }
241+ { showLyrics && (
242+ < div className = "absolute inset-0 z-20 bg-black/90 backdrop-blur-md flex flex-col" >
243+ { /* Fixed Corner Accents for Lyrics */ }
244+ < div className = "absolute top-0 left-0 w-4 h-4 border-t-2 border-l-2 border-cyan-500 z-30" > </ div >
245+ < div className = "absolute top-0 right-0 w-4 h-4 border-t-2 border-r-2 border-cyan-500 z-30" > </ div >
246+ < div className = "absolute bottom-0 left-0 w-4 h-4 border-b-2 border-l-2 border-cyan-500 z-30" > </ div >
247+ < div className = "absolute bottom-0 right-0 w-4 h-4 border-b-2 border-r-2 border-cyan-500 z-30" > </ div >
248+
249+ < div className = "flex-1 overflow-y-auto p-6 flex flex-col items-center justify-start text-center custom-scrollbar" >
250+ { loadingLyrics ? (
251+ < div className = "h-full flex items-center justify-center" >
252+ < div className = "text-cyan-500 animate-pulse font-bold tracking-widest uppercase text-xs" >
253+ [ FETCHING_LYRICS... ]
254+ </ div >
255+ </ div >
256+ ) : (
257+ < div className = "w-full mt-8" >
258+ < h3 className = "text-[10px] text-cyan-700 font-bold mb-4 tracking-[0.5em] border-b border-cyan-900/50 pb-2 uppercase" >
259+ DATA_STREAM: LYRICS
260+ </ h3 >
261+ < pre className = "whitespace-pre-wrap font-mono text-xs md:text-sm leading-relaxed text-cyan-300" >
262+ { lyrics || "NO_LYRICS_AVAILABLE" }
263+ </ pre >
264+ </ div >
265+ ) }
266+ </ div >
267+ </ div >
268+ ) }
202269 </ div >
203270
204271 { /* Track Info Display */ }
@@ -337,7 +404,7 @@ const CloudMusicPlayer = () => {
337404 < LinkIcon className = "text-cyan-700" size = { 16 } />
338405 < input
339406 type = "text"
340- placeholder = "DATA_SOURCE_URL "
407+ placeholder = "MP3_DATA_SOURCE_URL "
341408 value = { newUrl }
342409 onChange = { ( e ) => setNewUrl ( e . target . value ) }
343410 className = "flex-1 bg-transparent border-none p-2 text-cyan-300 placeholder-cyan-800 text-xs focus:outline-none"
0 commit comments