fix: lyrics sync via LRCLIB fallback + tab overflow fix

- Rename "Warteschlange" -> "Queue", tighten tab padding/letter-spacing
  so all 3 tabs fit on narrow screens
- _loadNpLyrics now tries Navidrome getLyricsBySongId first (works once
  .lrc sidecar is rescanned), then falls back to fetching synced LRC
  directly from LRCLIB and parsing [mm:ss.xx] timestamps in the frontend
- Plain text shown as last resort if no synced lyrics found

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
HoneyandClaude Sonnet 4.6 committed 2026-04-26 18:49:56 +02:00
1 parent fbabdf8269
commit f5e0af6e90
3 files changed
+66 -25

No files matched your search

+64 -23
View File
@@ -474,44 +474,85 @@ export function switchNpTab(tab) {
let _lyricsLines = [];
let _lyricsSongId = null;
function _parseLrc(text) {
const lines = [];
for(const raw of text.split('\n')) {
const m = raw.match(/^\[(\d+):(\d+(?:\.\d+)?)\](.*)/);
if(m) lines.push({ start: Math.round((parseInt(m[1])*60 + parseFloat(m[2]))*1000), value: m[3].trim() });
}
return lines.sort((a,b) => a.start - b.start).filter(l => l.value);
}
function _renderSyncedLines(content, lines) {
const top = document.createElement('div');
top.style.cssText = 'height:40vh;flex-shrink:0';
content.appendChild(top);
_lyricsLines = lines.map(line => {
const el = document.createElement('div');
el.className = 'lyrics-line';
el.textContent = line.value;
el.onclick = () => { S.audio.currentTime = line.start / 1000; };
content.appendChild(el);
return { start: line.start, el };
});
const bot = document.createElement('div');
bot.style.cssText = 'height:40vh;flex-shrink:0';
content.appendChild(bot);
updateLyricsHighlight(S.audio.currentTime * 1000);
}
async function _loadNpLyrics(songId) {
const content = document.getElementById('np-lyrics-content');
if(!content) return;
if(_lyricsSongId === songId && _lyricsLines.length) {
updateLyricsHighlight(S.audio.currentTime * 1000);
if(_lyricsSongId === songId && (_lyricsLines.length || content.querySelector('.lyrics-plain,.lyrics-none'))) {
if(_lyricsLines.length) updateLyricsHighlight(S.audio.currentTime * 1000);
return;
}
_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 s = S.queue[S.qIdx];
const { getLyricsBySongId } = await import('./api.js');
// 1. Try Navidrome structured lyrics (may already be synced if .lrc sidecar scanned)
const lyrics = await getLyricsBySongId(songId);
if(!lyrics.length) {
content.innerHTML = '<div style="text-align:center;padding:60px 0;color:rgba(255,255,255,.3);font-size:14px">Keine Lyrics verfügbar</div>';
const track = lyrics[0];
if(track?.synced && track.line?.length) {
content.innerHTML = '';
_renderSyncedLines(content, track.line.map(l => ({ start: l.start, value: l.value || '' })).filter(l => l.value));
return;
}
const track = lyrics[0];
// 2. Fallback: fetch synced lyrics directly from LRCLIB
let lrcText = null;
try {
const p = new URLSearchParams({ artist_name: s?.artist||'', track_name: s?.title||'' });
if(s?.album) p.set('album_name', s.album);
const r = await fetch(`https://lrclib.net/api/get?${p}`, { signal: AbortSignal.timeout(8000) });
if(r.ok) { const d = await r.json(); lrcText = d.syncedLyrics || null; }
} catch {}
content.innerHTML = '';
if(track.synced && track.line?.length) {
const spacer = document.createElement('div');
spacer.style.cssText = 'height:40vh;flex-shrink:0';
content.appendChild(spacer);
_lyricsLines = track.line.map(line => {
const el = document.createElement('div');
el.className = 'lyrics-line';
el.textContent = line.value || '';
if(line.value) el.onclick = () => { S.audio.currentTime = line.start / 1000; };
content.appendChild(el);
return { start: line.start, el };
});
const endSpacer = document.createElement('div');
endSpacer.style.cssText = 'height:40vh;flex-shrink:0';
content.appendChild(endSpacer);
updateLyricsHighlight(S.audio.currentTime * 1000);
} else if(track.line?.length) {
if(lrcText) {
const parsed = _parseLrc(lrcText);
if(parsed.length) { _renderSyncedLines(content, parsed); return; }
}
// 3. Plain text fallback (Navidrome unsynced or LRCLIB plainLyrics)
const plain = track?.line?.length
? track.line.map(l => l.value).join('\n')
: null;
if(plain) {
const el = document.createElement('div');
el.className = 'lyrics-plain';
el.textContent = track.line.map(l => l.value).join('\n');
el.textContent = plain;
content.appendChild(el);
} else {
const el = document.createElement('div');
el.className = 'lyrics-none';
el.style.cssText = 'text-align:center;padding:60px 0;color:rgba(255,255,255,.3);font-size:14px';
el.textContent = 'Keine Lyrics verfügbar';
content.appendChild(el);
}
}