fix: download notification opened the wrong song (same-name album)
The finished-download notification resolved the album by name via search3, then fell back to
a same-named album by a DIFFERENT artist (or the first result) when the exact artist match
wasn't found — e.g. right after download, before Navidrome rescans. Many artists share an album
title ("I LIKE IT!"), so the notification navigated to a completely different song.
- Web (notify.js) + Android (AppNavigation): require the artist to match when known (fuzzy for
multi-artist / album-artist drift); drop the name-only and first-result fallbacks. If nothing
matches, show "wird evtl. gerade gescannt" instead of opening a wrong album.
The download itself was correct all along (verified: it grabbed UPSAHL's official "I LIKE IT!"
visualizer) — only the notification's navigation was wrong. SW ns-v63.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
1e206e8df3
commit
17bb527e1d
4 files changed
+23
-11
No files matched your search
@@ -80,6 +80,7 @@ Big change: **the entire metadata/search stack moved from Apple's iTunes to Deez
|
||||
- **Backend** — Unit tests for the Deezer mapping/matching logic (`backend/test/deezer.test.js`, run with `npm test`), incl. per-track numbering, mixed-artist album-artist stability, explicit flags and the Japanese-title match guard. A live smoke test runs with `RUN_LIVE=1`
|
||||
|
||||
### Fixed
|
||||
- **Web + Android** — A finished-download **notification led to the wrong song**: it resolved the album by name via `search3`, and when the exact artist match wasn't found (e.g. the new album isn't scanned yet), it fell back to a same-named album by a **different** artist (or the first result). Several artists share an album title ("I LIKE IT!"), so tapping the notification opened a completely different song. Now the artist must match when known (fuzzy for multi-artist/album-artist drift); otherwise it shows "wird evtl. gerade gescannt" instead of opening a wrong album
|
||||
- **Web** — Downloading a not-yet-owned album from an artist page searched YouTube for "**Unknown Artist** <album>" and failed. Deezer's `/artist/{id}/albums` omits the `artist` object, so the discography cards had an empty artist name → the modal fell back to the literal `"Unknown Artist"`. Fixed by backfilling the known artist name in `deezerLookupArtistAlbums` and defaulting the download modal to **Album** for album cards (was always **Track**)
|
||||
- **Web + Android** — Owned albums were wrongly shown as **"Unvollständig 1 / 0 Tracks"** on the artist page. Deezer's `/artist/{id}/albums` list omits `nb_tracks`, so the total read as 0 and every matched album looked incomplete. Web now resolves the real track count from the Deezer album endpoint for **matched (owned) albums only** and treats an unknown total as complete; Android treats an unknown total as complete (never a false partial)
|
||||
|
||||
|
||||
@@ -576,13 +576,19 @@ fun AppNavigation() {
|
||||
val res = repo.search(n.navAlbum)
|
||||
val albums = res.album
|
||||
val norm = { s: String -> s.lowercase().trim() }
|
||||
val na = norm(n.navArtist)
|
||||
// REQUIRE the artist match when we know it — several artists share an
|
||||
// album title ("I LIKE IT!"); the old name-only / firstOrNull fallback
|
||||
// opened a completely different artist's album (wrong song).
|
||||
val match = albums.firstOrNull {
|
||||
norm(it.name) == norm(n.navAlbum) &&
|
||||
(n.navArtist.isBlank() || norm(it.artist ?: "") == norm(n.navArtist))
|
||||
} ?: albums.firstOrNull { norm(it.name) == norm(n.navAlbum) }
|
||||
?: albums.firstOrNull()
|
||||
val aa = norm(it.artist ?: "")
|
||||
norm(it.name) == norm(n.navAlbum) && na.isNotBlank() &&
|
||||
(aa == na || aa.contains(na) || na.contains(aa))
|
||||
} ?: if (na.isBlank()) albums.firstOrNull { norm(it.name) == norm(n.navAlbum) } else null
|
||||
if (match != null) {
|
||||
navController.navigate("album/${match.id}")
|
||||
} else {
|
||||
Toast.makeText(context, "Album noch nicht gefunden — evtl. wird die Library gerade gescannt", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
const CACHE = 'ns-v62';
|
||||
const CACHE = 'ns-v63';
|
||||
const PRECACHE = ['/', 'index.html', 'manifest.json', 'icon.svg'];
|
||||
const SKIP_PATHS = ['/rest/stream', '/rest/download', '/config.js'];
|
||||
// Auth token params stripped from cache keys so artwork survives across sessions/token refreshes.
|
||||
|
||||
+11
-6
@@ -121,16 +121,21 @@ export async function _notifNavigate(notifId) {
|
||||
try {
|
||||
const r = await api('search3', `&query=${encodeURIComponent(albumQuery)}&artistCount=0&albumCount=10&songCount=0`);
|
||||
const albums = r.searchResult3?.album || [];
|
||||
const match = albums.find(a => norm(a.name) === norm(albumQuery) && norm(a.artist) === norm(n.navArtist))
|
||||
|| albums.find(a => norm(a.name) === norm(albumQuery))
|
||||
|| albums[0];
|
||||
const na = norm(n.navArtist);
|
||||
const nameEq = a => norm(a.name) === norm(albumQuery);
|
||||
// Fuzzy artist compare (multi-artist / album-artist drift), but REQUIRE it when we know the
|
||||
// artist: several artists share an album title ("I LIKE IT!"), and a name-only fallback used
|
||||
// to open a completely different artist's album — the notification led to the wrong song.
|
||||
const artistEq = a => { const aa = norm(a.artist); return !!na && (aa === na || aa.includes(na) || na.includes(aa)); };
|
||||
const match = albums.find(a => nameEq(a) && artistEq(a))
|
||||
|| (na ? null : albums.find(nameEq)); // name-only only when the artist is unknown
|
||||
if (match) {
|
||||
navigate('album-detail', { id: match.id, name: match.name, artist: match.artist || '', artistId: match.artistId || '', coverArt: match.coverArt || '' });
|
||||
return;
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
toast('Album nicht gefunden — Library-Scan nötig?');
|
||||
toast('Album noch nicht gefunden — evtl. wird die Library gerade gescannt');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -138,9 +143,9 @@ export async function _notifNavigate(notifId) {
|
||||
try {
|
||||
const r = await api('search3', `&query=${encodeURIComponent(n.navArtist)}&artistCount=5&albumCount=0&songCount=0`);
|
||||
const artists = r.searchResult3?.artist || [];
|
||||
const match = artists.find(a => norm(a.name) === norm(n.navArtist)) || artists[0];
|
||||
const match = artists.find(a => norm(a.name) === norm(n.navArtist)); // no blind artists[0]
|
||||
if (match) { navigate('artist-detail', { id: match.id, name: match.name }); return; }
|
||||
} catch {}
|
||||
toast('Künstler nicht gefunden — Library-Scan nötig?');
|
||||
toast('Künstler noch nicht gefunden — evtl. wird die Library gerade gescannt');
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user