Skip to content

Commit 728eee7

Browse files
committed
lyrics feature enchanced
1 parent 296f716 commit 728eee7

File tree

3 files changed

+264
-204
lines changed

3 files changed

+264
-204
lines changed

Api/Songs.js

Lines changed: 56 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -2,100 +2,30 @@ import axios from "axios";
22
import { getCachedData, CACHE_GROUPS } from './CacheManager';
33

44
async function getSearchSongData(searchText, page, limit) {
5-
// Create a cache key based on the search parameters
65
const cacheKey = `search_${searchText}_page${page}_limit${limit}`;
7-
8-
// Define the fetch function that will be called if cache miss
6+
97
const fetchFunction = async () => {
10-
let config = {
11-
method: 'get',
12-
maxBodyLength: Infinity,
13-
url: `https://jiosavan-api-with-playlist.vercel.app/api/search/songs?query=${searchText}&page=${page}&limit=${limit}`,
14-
headers: { },
15-
};
16-
178
try {
18-
const response = await axios.request(config);
9+
const response = await axios.get(`https://jiosavan-api-with-playlist.vercel.app/api/search/songs?query=${searchText}&page=${page}&limit=${limit}`);
1910
return response.data;
20-
}
21-
catch (error) {
11+
} catch (error) {
12+
if (error.response && error.response.status >= 500) {
13+
console.error(`Server error fetching search data for "${searchText}":`, error.response.data);
14+
return { success: false, results: [], error: 'Server Error' };
15+
}
2216
throw error;
2317
}
2418
};
25-
26-
// Use cache manager with 5 minute expiration for search results
19+
2720
try {
2821
return await getCachedData(cacheKey, fetchFunction, 5, CACHE_GROUPS.SEARCH);
2922
} catch (error) {
30-
// If there's a storage error, try fetching directly without caching
31-
if (error.message && (error.message.includes('SQLITE_FULL') || error.message.includes('storage_full'))) {
32-
console.warn(`Storage full when searching "${searchText}", bypassing cache`);
33-
try {
34-
// Fetch directly without caching
35-
const data = await fetchFunction();
36-
return { ...data, fromCache: false, cacheBypass: true };
37-
} catch (fetchError) {
38-
console.error(`Direct fetch failed for "${searchText}":`, fetchError);
39-
throw fetchError;
40-
}
41-
}
42-
4323
console.error(`Error getting search data for "${searchText}":`, error);
44-
throw error;
24+
return { success: false, results: [], error: 'Network or Cache Error' };
4525
}
4626
}
4727

48-
async function getLyricsSongData(id) {
49-
// Create a cache key for lyrics - these rarely change so longer cache time
50-
const cacheKey = `lyrics_${id}`;
51-
52-
// Define the fetch function that will be called if cache miss
53-
const fetchFunction = async () => {
54-
let config = {
55-
method: 'get',
56-
maxBodyLength: Infinity,
57-
url: `https://jiosavan-api-with-playlist.vercel.app/api/songs/${id}/lyrics`,
58-
headers: { },
59-
};
60-
61-
try {
62-
const response = await axios.request(config);
63-
return response.data;
64-
}
65-
catch (error) {
66-
// Return a proper formatted response for 404 errors
67-
if (error.response && error.response.status === 404) {
68-
console.log(`No lyrics found for song ID ${id} (404)`);
69-
// Return a properly formatted empty result rather than throwing
70-
return {
71-
success: false,
72-
status: "NOT_FOUND",
73-
message: "No lyrics available for this song",
74-
data: { lyrics: "" }
75-
};
76-
}
77-
78-
// For other errors, log and rethrow
79-
console.error(`API error fetching lyrics for song ID ${id}:`,
80-
error.response ? `Status: ${error.response.status}` : error.message || error);
81-
throw error;
82-
}
83-
};
84-
85-
// Use cache manager with 1 day (1440 minutes) expiration for lyrics
86-
try {
87-
return await getCachedData(cacheKey, fetchFunction, 1440, CACHE_GROUPS.LYRICS);
88-
} catch (error) {
89-
console.error(`Error getting lyrics for song ID ${id}:`, error);
90-
// Return failure instead of throwing to prevent app crashes
91-
return {
92-
success: false,
93-
status: "ERROR",
94-
message: "Failed to get lyrics",
95-
data: { lyrics: "" }
96-
};
97-
}
98-
}
28+
9929

10030
async function getArtistSongs(artistId) {
10131
const cacheKey = `artist_songs_${artistId}`;
@@ -395,4 +325,49 @@ async function getArtistAlbumsPaginated(artistId, page = 1, limit = 10) {
395325
}
396326
}
397327

398-
export { getSearchSongData, getLyricsSongData, getArtistSongs, getArtistSongsPaginated, getAlbumSongs, getSearchArtistData, getArtistDetails, getArtistAlbums, getArtistAlbumsPaginated, validateArtist, filterValidArtists };
328+
async function getLyricsFromLrcLib(artist, title) {
329+
if (!artist || !title) {
330+
return { success: false, message: 'Missing artist or title' };
331+
}
332+
333+
// Extract the main song name before any parentheses
334+
const cleanTitle = title
335+
.split('(')[0] // Take only the part before the first parenthesis
336+
.split('[')[0] // Take only the part before the first bracket
337+
.replace(/\.{3}$/g, '') // Remove trailing ellipsis if any
338+
.replace(/\s+$/, '') // Remove any trailing whitespace
339+
.trim();
340+
341+
const cleanArtist = artist.split(',')[0].trim();
342+
343+
const cacheKey = `lrc_lib_${cleanArtist.toLowerCase()}_${cleanTitle.toLowerCase()}`;
344+
console.log(`Searching lyrics for: ${cleanArtist} - ${cleanTitle}`);
345+
346+
const fetchFunction = async () => {
347+
const urlsToTry = [
348+
`https://lrclib.net/api/search?artist_name=${encodeURIComponent(cleanArtist)}&track_name=${encodeURIComponent(cleanTitle)}`,
349+
`https://lrclib.net/api/search?artist_name=${encodeURIComponent(cleanArtist.split(' ')[0])}&track_name=${encodeURIComponent(cleanTitle)}`,
350+
];
351+
352+
for (const url of urlsToTry) {
353+
try {
354+
console.log('Trying URL:', url);
355+
const response = await fetch(url);
356+
if (response.ok) {
357+
const data = await response.json();
358+
if (data && data.length > 0 && (data[0].syncedLyrics || data[0].plainLyrics)) {
359+
return { success: true, data: data[0] };
360+
}
361+
}
362+
} catch (error) {
363+
console.error(`Search failed for URL ${url}:`, error);
364+
}
365+
}
366+
367+
return { success: false, message: 'No lyrics found' };
368+
};
369+
370+
return getCachedData(cacheKey, fetchFunction, 1440, CACHE_GROUPS.LYRICS);
371+
}
372+
373+
export { getSearchSongData, getArtistSongs, getArtistSongsPaginated, getAlbumSongs, getSearchArtistData, getArtistDetails, getArtistAlbums, getArtistAlbumsPaginated, validateArtist, filterValidArtists, getLyricsFromLrcLib };
Lines changed: 74 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -1,158 +1,109 @@
1-
import React, { useState, useEffect, useContext } from 'react';
1+
import React, { useState, useEffect, useContext, useCallback } from 'react';
22
import { ShowLyrics } from './ShowLyrics';
33
import { GetLyricsButton } from './GetLyricsButton';
4-
import Context from '../../Context/Context';
4+
import { getLyricsFromLrcLib } from '../../Api/Songs';
5+
6+
// Constants for error messages
7+
const ERROR_MESSAGES = {
8+
NO_TRACK: 'No song playing or missing track information. Please play a song first.',
9+
OFFLINE: 'You are offline. Lyrics are not available in offline mode.',
10+
NOT_FOUND: 'No Lyrics Found\nSorry, we couldn\'t find lyrics for this song.',
11+
EMPTY_LYRICS: 'No Lyrics Found\nLyrics data is empty for this song.',
12+
FETCH_ERROR: 'Could not fetch lyrics. Please try again.'
13+
};
514

6-
export const LyricsHandler = ({ currentPlayingTrack, isOffline, Index, onLyricsVisibilityChange, currentArtworkSource }) => {
7-
const [ShowDailog, setShowDailog] = useState(false);
8-
const [Lyric, setLyric] = useState(null);
9-
const [Loading, setLoading] = useState(false);
10-
const { currentPlaylistData } = useContext(Context);
15+
/**
16+
* Handles fetching and displaying lyrics for the currently playing track
17+
*/
18+
export const LyricsHandler = ({
19+
currentPlayingTrack,
20+
isOffline,
21+
onLyricsVisibilityChange,
22+
currentArtworkSource
23+
}) => {
24+
const [showDialog, setShowDialog] = useState(false);
25+
const [lyric, setLyric] = useState(null);
26+
const [isLoading, setIsLoading] = useState(false);
27+
28+
// Notify parent component about dialog visibility changes
29+
useEffect(() => {
30+
onLyricsVisibilityChange?.(showDialog);
31+
}, [showDialog, onLyricsVisibilityChange]);
1132

33+
// Clear lyrics when dialog is closed to prevent stale data
1234
useEffect(() => {
13-
if (onLyricsVisibilityChange) {
14-
onLyricsVisibilityChange(ShowDailog);
35+
if (!showDialog) {
36+
setLyric(null);
1537
}
16-
}, [ShowDailog, onLyricsVisibilityChange]);
17-
18-
const fetchLyrics = async () => {
19-
if (!currentPlayingTrack?.id) { // Ensure there's a track to fetch lyrics for
20-
setLyric({ plain: "No song selected." });
21-
setShowDailog(true);
38+
}, [currentPlayingTrack?.id, showDialog]);
39+
40+
/**
41+
* Fetches lyrics for the current track
42+
*/
43+
const fetchLyrics = useCallback(async () => {
44+
if (!currentPlayingTrack?.title || !currentPlayingTrack?.artist) {
45+
setLyric({ plain: ERROR_MESSAGES.NO_TRACK });
46+
setShowDialog(true);
2247
return;
2348
}
24-
setShowDailog(true);
25-
setLoading(true);
49+
50+
setShowDialog(true);
51+
setIsLoading(true);
2652
setLyric(null);
2753

2854
try {
29-
if (!currentPlayingTrack?.title || !currentPlayingTrack?.artist) {
30-
setLyric({ plain: "No song playing or missing track information. Please play a song first." });
31-
setLoading(false);
32-
return;
33-
}
34-
3555
if (isOffline) {
36-
setLyric({ plain: "You are offline. Lyrics are not available in offline mode." });
37-
setLoading(false);
56+
setLyric({ plain: ERROR_MESSAGES.OFFLINE });
3857
return;
3958
}
4059

41-
let titleForAPI = null;
42-
let artistForAPI = null;
43-
let usedContextData = false;
44-
45-
if (
46-
currentPlaylistData &&
47-
currentPlaylistData.length > 0 &&
48-
Index >= 0 &&
49-
Index < currentPlaylistData.length &&
50-
currentPlayingTrack &&
51-
currentPlaylistData[Index] &&
52-
currentPlaylistData[Index].id === currentPlayingTrack.id
53-
) {
54-
const trackFromPlaylist = currentPlaylistData[Index];
55-
if (trackFromPlaylist.title && trackFromPlaylist.artist) {
56-
titleForAPI = trackFromPlaylist.title;
57-
artistForAPI = trackFromPlaylist.artist;
58-
usedContextData = true;
59-
// console.log(`LyricsHandler: Using title/artist from currentPlaylistData[${Index}]`);
60-
}
61-
}
62-
63-
if (!titleForAPI && currentPlayingTrack?.title) {
64-
titleForAPI = currentPlayingTrack.title;
65-
}
66-
if (!artistForAPI && currentPlayingTrack?.artist) {
67-
artistForAPI = currentPlayingTrack.artist;
68-
}
60+
const { artist, title } = currentPlayingTrack;
61+
const lyricsData = await getLyricsFromLrcLib(artist, title);
6962

70-
if (!usedContextData) {
71-
if (titleForAPI && titleForAPI.endsWith("...")) {
72-
titleForAPI = titleForAPI.substring(0, titleForAPI.length - 3).trim();
73-
}
74-
if (artistForAPI && artistForAPI.endsWith("...")) {
75-
artistForAPI = artistForAPI.substring(0, artistForAPI.length - 3).trim();
76-
}
77-
if (artistForAPI && artistForAPI.includes(',')) {
78-
artistForAPI = artistForAPI.split(',')[0].trim();
79-
}
80-
}
81-
82-
if (!titleForAPI || !artistForAPI) {
83-
setLyric({ plain: "Missing track information for API call." });
84-
setLoading(false);
63+
if (!lyricsData?.success) {
64+
setLyric({ plain: lyricsData?.message || ERROR_MESSAGES.NOT_FOUND });
8565
return;
8666
}
8767

88-
const trackName = encodeURIComponent(titleForAPI);
89-
const artistName = encodeURIComponent(artistForAPI);
68+
const { syncedLyrics, plainLyrics } = lyricsData.data || {};
9069

91-
// Construct the full API URL. Assuming lrclib.net structure.
92-
// This was: `https://lrclib.net/api/search?artist_name=${artistName}&track_name=${trackName}`
93-
// The getLyricsSongData function might encapsulate this, or expect parts.
94-
const apiUrl = `https://lrclib.net/api/search?artist_name=${artistName}&track_name=${trackName}`;
95-
// console.log(`LyricsHandler: Fetching lyrics from: ${apiUrl}`);
96-
const apiResponse = await fetch(apiUrl);
97-
98-
if (!apiResponse.ok) {
99-
let errorMessage = `Failed to fetch lyrics. Status: ${apiResponse.status}`;
100-
if (apiResponse.status === 404) {
101-
errorMessage = "No Lyrics Found\nSorry, we couldn't find lyrics for this song.";
102-
} else {
103-
try {
104-
const errorData = await apiResponse.json();
105-
errorMessage = errorData.message || `Service Error (${apiResponse.status})\nPlease try again later.`;
106-
} catch (e) {
107-
errorMessage = `Service Error (${apiResponse.status})\nPlease try again later.`;
108-
}
109-
}
110-
setLyric({ plain: errorMessage });
111-
console.error('LyricsHandler: Lyrics fetch error:', errorMessage);
112-
setLoading(false);
113-
return;
114-
}
115-
116-
const results = await apiResponse.json();
117-
118-
if (results && results.length > 0) {
119-
const firstMatch = results[0];
120-
// console.log('LyricsHandler: Lyrics API Response:', firstMatch);
121-
if (firstMatch.syncedLyrics) {
122-
setLyric({ synced: firstMatch.syncedLyrics, plain: firstMatch.plainLyrics });
123-
} else if (firstMatch.plainLyrics) {
124-
setLyric({ plain: firstMatch.plainLyrics });
125-
} else {
126-
setLyric({ plain: "No Lyrics Found\nLyrics data is empty for this song." });
127-
}
70+
if (syncedLyrics) {
71+
setLyric({ synced: syncedLyrics, plain: plainLyrics });
72+
} else if (plainLyrics) {
73+
setLyric({ plain: plainLyrics });
12874
} else {
129-
setLyric({ plain: "No Lyrics Found\nSorry, we couldn't find lyrics for this song." });
75+
setLyric({ plain: ERROR_MESSAGES.EMPTY_LYRICS });
13076
}
13177
} catch (error) {
132-
console.error("Error fetching lyrics in LyricsHandler:", error);
133-
setLyric({ plain: "Could not fetch lyrics. Please try again." });
78+
console.error('Error fetching lyrics:', error);
79+
setLyric({ plain: ERROR_MESSAGES.FETCH_ERROR });
13480
} finally {
135-
setLoading(false);
136-
}
137-
};
138-
139-
// Effect to clear lyrics when track changes if dialog is not open, to prevent stale lyrics briefly showing
140-
useEffect(() => {
141-
if (!ShowDailog) {
142-
setLyric(null);
81+
setIsLoading(false);
14382
}
144-
}, [currentPlayingTrack?.id, ShowDailog]);
83+
}, [currentPlayingTrack, isOffline]);
14584

14685
return (
14786
<>
14887
<GetLyricsButton onPress={fetchLyrics} />
14988
<ShowLyrics
150-
ShowDailog={ShowDailog}
151-
Loading={Loading}
152-
Lyric={Lyric}
153-
setShowDailog={setShowDailog}
89+
ShowDailog={showDialog}
90+
Loading={isLoading}
91+
Lyric={lyric}
92+
setShowDailog={setShowDialog}
15493
currentArtworkSource={currentArtworkSource}
15594
/>
15695
</>
15796
);
15897
};
98+
99+
// Add prop type validation if needed
100+
// LyricsHandler.propTypes = {
101+
// currentPlayingTrack: PropTypes.shape({
102+
// title: PropTypes.string,
103+
// artist: PropTypes.string,
104+
// id: PropTypes.string,
105+
// }),
106+
// isOffline: PropTypes.bool,
107+
// onLyricsVisibilityChange: PropTypes.func,
108+
// currentArtworkSource: PropTypes.any,
109+
// };

0 commit comments

Comments
 (0)