diff --git a/backend/novasonic-server.js b/backend/novasonic-server.js index 7aa104e..b068b51 100644 --- a/backend/novasonic-server.js +++ b/backend/novasonic-server.js @@ -145,7 +145,7 @@ async function lookupItunesTrackMeta(artist, title) { const match = results.find(t => norm(t.trackName) === norm(title) && norm(t.artistName).split(' ')[0] === norm(artist).split(' ')[0] - ) || results[0]; + ); if (!match) return {}; return { album: match.collectionName || '', diff --git a/src/components.js b/src/components.js index abf0e5f..7396a1c 100644 --- a/src/components.js +++ b/src/components.js @@ -27,19 +27,24 @@ export function makeAlbumCard(a) { } // ─── SONG LIST ITEM (library) ─── -export function makeSongItem(s, idx, list) { +export function makeSongItem(s, idx, list, { noArtistLink = false, noAlbumLink = false } = {}) { const el = document.createElement('div'); if(s.starred && !s._starred) s._starred = true; el.className = 'song-item' + (S.queue[S.qIdx]?.id === s.id ? ' playing' : ''); const cv = coverUrl(s.coverArt, 60); + const artistHtml = s.artistId||s.artist + ? (noArtistLink + ? `${esc(s.artist||'')}` + : `${esc(s.artist||'')}`) + : ''; + const albumHtml = !noAlbumLink && (s.albumId||s.album) + ? ` · ${esc(s.album||'')}` + : ''; el.innerHTML = `
${idx+1}
${cv ? `` : '
♪
'}
${esc(s.title)}
-
- ${s.artistId||s.artist ? `${esc(s.artist||'')}` : ''} - ${s.albumId||s.album ? ` · ${esc(s.album||'')}` : ''} -
+
${artistHtml}${albumHtml}
${formatDur(s.duration)} diff --git a/src/nowplaying.js b/src/nowplaying.js index fc37bdc..300b59a 100644 --- a/src/nowplaying.js +++ b/src/nowplaying.js @@ -80,6 +80,7 @@ export function switchNpTab(tab) { // ─── LYRICS ─── let _lyricsLines = []; let _lyricsSongId = null; +let _lyricsLoadToken = 0; const LYRICS_LOOKAHEAD_MS = 200; @@ -125,11 +126,14 @@ async function _loadNpLyrics(songId) { _lyricsLines = []; content.innerHTML = '
Lade Lyrics…
'; + const token = ++_lyricsLoadToken; const s = S.queue[S.qIdx]; const { getLyricsBySongId } = await import('./api.js'); + if(token !== _lyricsLoadToken) return; // 1. Try Navidrome structured lyrics const lyrics = await getLyricsBySongId(songId); + if(token !== _lyricsLoadToken) return; const track = lyrics[0]; if(track?.synced && track.line?.length) { content.innerHTML = ''; @@ -149,6 +153,7 @@ async function _loadNpLyrics(songId) { ...(s?.album ? [new URLSearchParams({ artist_name: firstArtist, track_name: s?.title||'' })] : []), ]; for(const p of attempts) { + if(token !== _lyricsLoadToken) return; const r = await fetch(`https://lrclib.net/api/get?${p}`, { signal: AbortSignal.timeout(6000) }); if(r.ok) { const d = await r.json(); @@ -158,6 +163,7 @@ async function _loadNpLyrics(songId) { } } } catch {} + if(token !== _lyricsLoadToken) return; content.innerHTML = ''; if(lrcText) { @@ -194,7 +200,14 @@ export function updateLyricsHighlight(currentMs) { line.el.classList.toggle('active', i === activeIdx); line.el.classList.toggle('past', i < activeIdx); }); - if(activeIdx >= 0) _lyricsLines[activeIdx].el.scrollIntoView({ behavior: 'smooth', block: 'center' }); + if(activeIdx >= 0) { + const activeEl = _lyricsLines[activeIdx].el; + const container = document.getElementById('np-lyrics-view'); + if(container) { + const target = activeEl.offsetTop - container.clientHeight / 2 + activeEl.clientHeight / 2; + container.scrollTo({ top: Math.max(0, target), behavior: 'smooth' }); + } + } } export function onNpSongChange(songId) { diff --git a/src/views.js b/src/views.js index a4bf0df..e19ea53 100644 --- a/src/views.js +++ b/src/views.js @@ -440,7 +440,7 @@ export async function loadAlbumDetail(album) { toast(`⬇ ${songs.length} Titel Download gestartet`); }; sl.innerHTML = ''; - songs.forEach((s,i) => sl.appendChild(makeSongItem(s, i, songs))); + songs.forEach((s,i) => sl.appendChild(makeSongItem(s, i, songs, { noArtistLink: true, noAlbumLink: true }))); } catch(e) { sl.innerHTML = `

${e.message}

`; } } @@ -492,7 +492,7 @@ export async function loadPlaylistDetail(pl) { playQueue(shuffled, 0); }; sl.innerHTML = ''; - songs.forEach((s,i) => sl.appendChild(makeSongItem(s, i, songs))); + songs.forEach((s,i) => sl.appendChild(makeSongItem(s, i, songs, { noArtistLink: true }))); } catch(e) { sl.innerHTML = `

${e.message}

`; } }