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:
1 parent
fbabdf8269
commit
f5e0af6e90
3 files changed
+66
-25
No files matched your search
+1
-1
@@ -558,7 +558,7 @@
|
||||
<div class="np-tabs">
|
||||
<button class="np-tab active" id="np-tab-player" onclick="switchNpTab('player')">Jetzt läuft</button>
|
||||
<button class="np-tab" id="np-tab-lyrics" onclick="switchNpTab('lyrics')">Lyrics</button>
|
||||
<button class="np-tab" id="np-tab-queue" onclick="switchNpTab('queue')">Warteschlange</button>
|
||||
<button class="np-tab" id="np-tab-queue" onclick="switchNpTab('queue')">Queue</button>
|
||||
</div>
|
||||
<button class="np-menu-btn" onclick="openNpMenu()" title="Optionen">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="5" r="1.5" fill="currentColor"/><circle cx="12" cy="12" r="1.5" fill="currentColor"/><circle cx="12" cy="19" r="1.5" fill="currentColor"/></svg>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -608,7 +608,7 @@ nav{flex:1;overflow-y:auto;padding:4px 10px}
|
||||
.np-icon-btn:hover{color:#fff;background:rgba(255,255,255,.08)}
|
||||
/* NP tabs */
|
||||
.np-tabs{display:flex;gap:2px;background:rgba(255,255,255,.08);border-radius:20px;padding:3px;flex:1;min-width:0}
|
||||
.np-tab{background:none;border:none;color:rgba(255,255,255,.45);font-size:10px;font-weight:700;letter-spacing:.4px;text-transform:uppercase;padding:5px 10px;border-radius:16px;cursor:pointer;transition:all .2s;font-family:var(--font);flex:1;white-space:nowrap}
|
||||
.np-tab{background:none;border:none;color:rgba(255,255,255,.45);font-size:10px;font-weight:700;letter-spacing:0;text-transform:uppercase;padding:5px 7px;border-radius:16px;cursor:pointer;transition:all .2s;font-family:var(--font);flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||||
.np-tab.active{background:rgba(255,255,255,.15);color:#fff}
|
||||
/* NP queue view */
|
||||
#np-player-view{display:flex;flex-direction:column;align-items:center;justify-content:center;width:100%;flex:1;min-height:0}
|
||||
|
||||
Reference in new issue
Block a user