Skip to content

Commit 9a32103

Browse files
committed
feat(music_player): lyrics support
1 parent 622cf2a commit 9a32103

File tree

2 files changed

+76
-4
lines changed

2 files changed

+76
-4
lines changed

public/media_player/musics.piml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,8 @@
3939
(title) Dose of Reality
4040
(artist) Nonak
4141
(url) https://github.com/fezcode/fcdx.cdn/releases/download/TheTag/Dose_of_Reality-Nonak.mp3
42+
43+
> (track)
44+
(title) Lights Please
45+
(artist) J.Cole
46+
(url) https://github.com/fezcode/fcdx.cdn/releases/download/TheTag/J.Cole_Lights_Please.mp3

src/app/apps/CloudMusicPlayer/CloudMusicPlayer.jsx

Lines changed: 71 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useState } from 'react';
1+
import React, { useState, useEffect } from 'react';
22
import { Link } from 'react-router-dom';
33
import CloudPlaylist from './components/CloudPlaylist';
44
import './CloudMusicPlayer.css';
@@ -8,7 +8,7 @@ import {
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';
1313
import CustomSlider from '../../../components/CustomSlider';
1414
import 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

Comments
 (0)