feat(lyrics): prefetch on play + smooth measured-position autoscroll
Warm the lrclib cache the moment a track starts (Android PlayerController onMediaItemTransition; web onNpSongChange) so opening the Lyrics tab is instant. Extract a DOM-free fetch helper on web for reuse. Rewrite Android lyrics autoscroll to center on the active line's measured position in a LazyColumn instead of a fixed line-height estimate — removes stutter and the downward drift on wrapped lines; active line sits at a ~40%-from-top reading anchor. CHANGELOG: fold into 1.0; drop the now-fixed lyrics known-issue note. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
7a5197b776
commit
211b8ec6c2
4 files changed
+87
-46
No files matched your search
+33
-16
@@ -145,7 +145,6 @@ async function _loadNpLyrics(songId) {
|
||||
}
|
||||
_lyricsSongId = songId;
|
||||
_lyricsLines = [];
|
||||
content.innerHTML = '<div style="text-align:center;padding:60px 0;color:rgba(255,255,255,.3);font-size:14px">Lade Lyrics…</div>';
|
||||
|
||||
const token = ++_lyricsLoadToken;
|
||||
const s = S.queue[S.qIdx];
|
||||
@@ -155,6 +154,28 @@ async function _loadNpLyrics(songId) {
|
||||
const dur = s?.duration || 0;
|
||||
const cacheKey = `${artist}|${title}`;
|
||||
|
||||
// Cache hit (warmed on play, or from a previous view) — render instantly, no placeholder flash
|
||||
if(_lyricsCache.has(cacheKey)) {
|
||||
content.innerHTML = '';
|
||||
_showLyricsResult(content, _lyricsCache.get(cacheKey));
|
||||
return;
|
||||
}
|
||||
|
||||
content.innerHTML = '<div style="text-align:center;padding:60px 0;color:rgba(255,255,255,.3);font-size:14px">Lade Lyrics…</div>';
|
||||
const result = await _fetchLyricsData(artist, title, album, dur);
|
||||
if(token !== _lyricsLoadToken) return;
|
||||
content.innerHTML = '';
|
||||
_showLyricsResult(content, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pure lrclib fetch cascade — no DOM. Populates _lyricsCache and returns the result.
|
||||
* Shared by the Lyrics tab and by prefetchLyrics() so opening Lyrics feels instant.
|
||||
*/
|
||||
async function _fetchLyricsData(artist, title, album, dur) {
|
||||
const cacheKey = `${artist}|${title}`;
|
||||
if(_lyricsCache.has(cacheKey)) return _lyricsCache.get(cacheKey);
|
||||
|
||||
const SIG = () => AbortSignal.timeout(6000);
|
||||
const safeJson = async (promise) => { try { const r = await promise; return r.ok ? r.json() : null; } catch { return null; } };
|
||||
const bestMatch = (candidates, durSecs) => {
|
||||
@@ -166,19 +187,12 @@ async function _loadNpLyrics(songId) {
|
||||
return Math.abs(pick.duration - durSecs) <= 5 ? pick : (synced[0] || candidates[0]);
|
||||
};
|
||||
|
||||
// Cache hit — instant
|
||||
if(_lyricsCache.has(cacheKey)) {
|
||||
const cached = _lyricsCache.get(cacheKey);
|
||||
content.innerHTML = '';
|
||||
_showLyricsResult(content, cached);
|
||||
return;
|
||||
}
|
||||
let result = null;
|
||||
|
||||
// 1a. lrclib exact match with duration
|
||||
const exactP = new URLSearchParams({ artist_name: artist, track_name: title });
|
||||
if(album) exactP.set('album_name', album);
|
||||
if(dur) exactP.set('duration', dur);
|
||||
let result = null;
|
||||
try {
|
||||
const r = await fetch(`https://lrclib.net/api/get?${exactP}`, { signal: SIG() });
|
||||
if(r.status === 200) {
|
||||
@@ -199,8 +213,6 @@ async function _loadNpLyrics(songId) {
|
||||
} catch {}
|
||||
}
|
||||
|
||||
if(token !== _lyricsLoadToken) return;
|
||||
|
||||
// 2+3. lrclib search — artist+title and title-only in parallel (2 requests, not 3)
|
||||
if(!result) {
|
||||
const artistP = new URLSearchParams({ track_name: title });
|
||||
@@ -214,12 +226,15 @@ async function _loadNpLyrics(songId) {
|
||||
result = bestMatch(artistData, dur) ?? bestMatch(titleData, dur);
|
||||
}
|
||||
|
||||
if(token !== _lyricsLoadToken) return;
|
||||
|
||||
if(token !== _lyricsLoadToken) return;
|
||||
_lyricsCache.set(cacheKey, result ?? null);
|
||||
content.innerHTML = '';
|
||||
_showLyricsResult(content, result);
|
||||
return result ?? null;
|
||||
}
|
||||
|
||||
/** Warm the lyrics cache when a track starts, so opening the Lyrics tab is instant. */
|
||||
function prefetchLyrics(song) {
|
||||
if(!song || !song.title) return;
|
||||
const artist = (song.artist || '').split(/[/,]/)[0].trim();
|
||||
_fetchLyricsData(artist, song.title, song.album || '', song.duration || 0).catch(() => {});
|
||||
}
|
||||
|
||||
function _showLyricsResult(content, result) {
|
||||
@@ -268,6 +283,8 @@ export function onNpSongChange(songId) {
|
||||
_lyricsLines = [];
|
||||
const content = document.getElementById('np-lyrics-content');
|
||||
if(content) content.innerHTML = '';
|
||||
// Warm the cache the moment the song changes, so the Lyrics tab opens instantly.
|
||||
prefetchLyrics(S.queue.find(x => x.id === songId) || S.queue[S.qIdx]);
|
||||
if(S.npLyricsMode) _loadNpLyrics(songId);
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user