fix(lyrics): stop hammering lrclib (caused IP throttling → no lyrics)

Rapid skips + lyrics prefetch fired the full lrclib cascade many times over,
so lrclib throttled the IP and requests timed out (canceled) on both web and
Android. Reduce the request volume:

- Web: de-dupe concurrent fetches per song (prefetch + tab-open now share one
  cascade); bump the per-request timeout 6s → 8s.
- Android: per-song Mutex so prefetch + on-open load don't both run the cascade;
  send a proper User-Agent (lrclib asks for one and treats it better); debounce
  the prefetch (1.5s, cancelled on the next skip) instead of firing immediately
  on every track change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
HoneyandClaude Opus 4.8 committed 2026-06-28 21:12:01 +02:00
1 parent ddb9418603
commit 63a7260c94
3 files changed
+34 -3

No files matched your search

+10 -1
View File
@@ -102,6 +102,7 @@ let _lyricsSongId = null;
let _lyricsLoadToken = 0;
let _prefetchTimer = null;
const _lyricsCache = new Map(); // "artist|title" → result object (null = not found)
const _lyricsInflight = new Map(); // "artist|title" → Promise — de-dupe concurrent fetches
const LYRICS_LOOKAHEAD_MS = 200;
@@ -176,8 +177,16 @@ async function _loadNpLyrics(songId) {
async function _fetchLyricsData(artist, title, album, dur) {
const cacheKey = `${artist}|${title}`;
if(_lyricsCache.has(cacheKey)) return _lyricsCache.get(cacheKey);
// De-dupe concurrent fetches for the same song (prefetch + tab-open) so we don't fire the
// whole lrclib cascade twice — that doubles the request rate and gets the IP throttled.
if(_lyricsInflight.has(cacheKey)) return _lyricsInflight.get(cacheKey);
const p = _doFetchLyrics(artist, title, album, dur, cacheKey);
_lyricsInflight.set(cacheKey, p);
try { return await p; } finally { _lyricsInflight.delete(cacheKey); }
}
const SIG = () => AbortSignal.timeout(6000);
async function _doFetchLyrics(artist, title, album, dur, cacheKey) {
const SIG = () => AbortSignal.timeout(8000);
const safeJson = async (promise) => { try { const r = await promise; return r.ok ? r.json() : null; } catch { return null; } };
const bestMatch = (candidates, durSecs) => {
if(!candidates?.length) return null;