diff --git a/apps/api/src/services/lyrics.ts b/apps/api/src/services/lyrics.ts index 776e2c1..ddf06fd 100644 --- a/apps/api/src/services/lyrics.ts +++ b/apps/api/src/services/lyrics.ts @@ -65,10 +65,8 @@ async function lookup(track: { title: string; artist: string; album: string | nu if (track.durationMs) { const params = new URLSearchParams({ track_name: track.title, artist_name: artist, duration: String(Math.round(track.durationMs / 1000)) }); if (track.album) params.set('album_name', track.album); - const exact = await fetchJson(`${env.LRCLIB_URL}/api/get?${params}`, { headers }).catch((e: unknown) => { - if (e instanceof HttpError && e.status === 404) return null; - throw e; - }); + // Not found, or LRCLIB's exact lookup is overloaded (it rate-limits /api/get separately): try the search. + const exact = await fetchJson(`${env.LRCLIB_URL}/api/get?${params}`, { headers }).catch(() => null); if (exact) return exact; } const params = new URLSearchParams({ track_name: track.title, artist_name: artist }); diff --git a/apps/api/test/lyrics.test.ts b/apps/api/test/lyrics.test.ts index 5b8b1e4..9be33b0 100644 --- a/apps/api/test/lyrics.test.ts +++ b/apps/api/test/lyrics.test.ts @@ -55,6 +55,20 @@ describe('lyrics', () => { expect(body.plain).toBe('the one'); }); + it("uses the search when LRCLIB's exact lookup is rate-limited", async () => { + const { id } = await upsertTrack({ title: 'Midnight City', artist: 'M83', durationMs: 241_000 }); + vi.stubGlobal( + 'fetch', + routeFetch([ + [/\/api\/get\?/, () => json({ message: 'slow down' }, 429)], + [/\/api\/search\?/, () => json([record()])], + ]), + ); + const res = await lyrics(id); + expect(res.statusCode).toBe(200); + expect(res.json().synced).toHaveLength(2); + }); + it('returns null when nothing matches, and 503 when LRCLIB is down', async () => { const { id } = await upsertTrack({ title: 'Unknown', artist: 'Nobody', durationMs: 100_000 }); vi.stubGlobal('fetch', routeFetch([[/lrclib/, () => json({ message: 'down' }, 500)]])); diff --git a/apps/mobile/src/app/(tabs)/index.tsx b/apps/mobile/src/app/(tabs)/index.tsx index 0cf410b..35a94c0 100644 --- a/apps/mobile/src/app/(tabs)/index.tsx +++ b/apps/mobile/src/app/(tabs)/index.tsx @@ -1,7 +1,7 @@ import { router } from 'expo-router'; import { useEffect, useState } from 'react'; import { Pressable, RefreshControl, ScrollView, StyleSheet, Text, View } from 'react-native'; -import Animated, { FadeIn, LinearTransition } from 'react-native-reanimated'; +import Animated, { FadeIn } from 'react-native-reanimated'; import { SafeAreaView } from 'react-native-safe-area-context'; import useSWR, { useSWRConfig } from 'swr'; import { sortFeed, type ConnectionDTO, type FeedItemDTO, type FollowListDTO, type ProfileDTO } from '@musicshare/shared'; @@ -85,9 +85,9 @@ export default function FeedScreen() { return ( - {/* Cards fade in and slide to new positions when someone pins or starts playing. */} + {/* No itemLayoutAnimation: it leaves the cards behind when the header above them changes height + (e.g. your own card's lyrics loading), opening a gap under "Friends listening". */} item.user.id} contentContainerStyle={styles.list}