Fix a gap under Friends listening, and lyrics failing while LRCLIB's exact lookup is overloaded
The feed's itemLayoutAnimation left the cards behind when the header above them shrank (your own card's lyrics loading). LRCLIB rate-limits /api/get on its own; fall back to search. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011Y2iUsKEWDuRGjK5FPmcUP
This commit is contained in:
@@ -65,10 +65,8 @@ async function lookup(track: { title: string; artist: string; album: string | nu
|
|||||||
if (track.durationMs) {
|
if (track.durationMs) {
|
||||||
const params = new URLSearchParams({ track_name: track.title, artist_name: artist, duration: String(Math.round(track.durationMs / 1000)) });
|
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);
|
if (track.album) params.set('album_name', track.album);
|
||||||
const exact = await fetchJson<LrclibRecord>(`${env.LRCLIB_URL}/api/get?${params}`, { headers }).catch((e: unknown) => {
|
// Not found, or LRCLIB's exact lookup is overloaded (it rate-limits /api/get separately): try the search.
|
||||||
if (e instanceof HttpError && e.status === 404) return null;
|
const exact = await fetchJson<LrclibRecord>(`${env.LRCLIB_URL}/api/get?${params}`, { headers }).catch(() => null);
|
||||||
throw e;
|
|
||||||
});
|
|
||||||
if (exact) return exact;
|
if (exact) return exact;
|
||||||
}
|
}
|
||||||
const params = new URLSearchParams({ track_name: track.title, artist_name: artist });
|
const params = new URLSearchParams({ track_name: track.title, artist_name: artist });
|
||||||
|
|||||||
@@ -55,6 +55,20 @@ describe('lyrics', () => {
|
|||||||
expect(body.plain).toBe('the one');
|
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 () => {
|
it('returns null when nothing matches, and 503 when LRCLIB is down', async () => {
|
||||||
const { id } = await upsertTrack({ title: 'Unknown', artist: 'Nobody', durationMs: 100_000 });
|
const { id } = await upsertTrack({ title: 'Unknown', artist: 'Nobody', durationMs: 100_000 });
|
||||||
vi.stubGlobal('fetch', routeFetch([[/lrclib/, () => json({ message: 'down' }, 500)]]));
|
vi.stubGlobal('fetch', routeFetch([[/lrclib/, () => json({ message: 'down' }, 500)]]));
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { router } from 'expo-router';
|
import { router } from 'expo-router';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Pressable, RefreshControl, ScrollView, StyleSheet, Text, View } from 'react-native';
|
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 { SafeAreaView } from 'react-native-safe-area-context';
|
||||||
import useSWR, { useSWRConfig } from 'swr';
|
import useSWR, { useSWRConfig } from 'swr';
|
||||||
import { sortFeed, type ConnectionDTO, type FeedItemDTO, type FollowListDTO, type ProfileDTO } from '@musicshare/shared';
|
import { sortFeed, type ConnectionDTO, type FeedItemDTO, type FollowListDTO, type ProfileDTO } from '@musicshare/shared';
|
||||||
@@ -85,9 +85,9 @@ export default function FeedScreen() {
|
|||||||
return (
|
return (
|
||||||
<SafeAreaView style={styles.safe} edges={['top']}>
|
<SafeAreaView style={styles.safe} edges={['top']}>
|
||||||
<AmbientBackground />
|
<AmbientBackground />
|
||||||
{/* 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". */}
|
||||||
<Animated.FlatList
|
<Animated.FlatList
|
||||||
itemLayoutAnimation={LinearTransition.duration(200)}
|
|
||||||
data={feed ?? []}
|
data={feed ?? []}
|
||||||
keyExtractor={(item) => item.user.id}
|
keyExtractor={(item) => item.user.id}
|
||||||
contentContainerStyle={styles.list}
|
contentContainerStyle={styles.list}
|
||||||
|
|||||||
Reference in New Issue
Block a user