feat(web+android): search play queues only the tapped song (radio fills the rest)

Playing a song from search results queued the whole result list. Now it plays only
the clicked song; Radio mode, if on, fills the rest — closer to Spotify. Both
platforms already trigger a radio top-up on the first track (web playQueue →
fillRadioQueue; Android onMediaItemTransition → checkRadioRefill), so no extra wiring
is needed. Album/playlist play still queues the full list. Bump SW cache to ns-v44.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
HoneyandClaude Opus 4.8 committed 2026-07-25 22:00:58 +02:00
1 parent 14fd5119f2
commit 1e242796fe
5 files changed
+9 -7

No files matched your search

@@ -343,7 +343,8 @@ private fun LibrarySearchContent(
coverUrl = coverUrl(song.coverArt),
isPlaying = playerState.currentSong?.id == song.id,
starred = song.starred != null,
onPlay = { onSongPlay(songs, idx) },
// Play only the tapped song; Radio mode (if on) fills the rest.
onPlay = { onSongPlay(listOf(song), 0) },
onMore = onShowContextMenu?.let { cb -> { cb(song) } },
)
}
@@ -57,12 +57,12 @@ fun SearchScreen(
if (r.song.isNotEmpty()) {
item { ResultHeader("Songs") }
items(r.song, key = { it.id }) { song ->
val idx = r.song.indexOf(song)
SongRow(
song = song,
coverUrl = coverUrl(song.coverArt),
isPlaying = playerState.currentSong?.id == song.id,
onPlay = { onSongPlay(r.song, idx) },
// Play only the tapped song; Radio mode (if on) fills the rest.
onPlay = { onSongPlay(listOf(song), 0) },
)
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
const CACHE = 'ns-v43';
const CACHE = 'ns-v44';
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.
+3 -2
View File
@@ -35,7 +35,7 @@ export function makeAlbumCard(a) {
}
// ─── SONG LIST ITEM (library) ───
export function makeSongItem(s, idx, list, { noArtistLink = false } = {}) {
export function makeSongItem(s, idx, list, { noArtistLink = false, singlePlay = 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' : '');
@@ -69,7 +69,8 @@ export function makeSongItem(s, idx, list, { noArtistLink = false } = {}) {
const btns = el.querySelectorAll('.icon-btn');
btns[0].onclick = (e) => { e.stopPropagation(); toggleStar(s, btns[0]); };
btns[1].onclick = (e) => { e.stopPropagation(); showCtxMenu(e, s, list, idx); };
el.onclick = () => playQueue(list, idx, { noShuffle: true });
// singlePlay (search results): play only the clicked song; Radio mode (if on) fills the rest.
el.onclick = () => singlePlay ? playQueue([s], 0, { noShuffle: true }) : playQueue(list, idx, { noShuffle: true });
el.addEventListener('contextmenu', e => { e.preventDefault(); showCtxMenu(e, s, list, idx); });
return el;
}
+1 -1
View File
@@ -82,7 +82,7 @@ export async function doLocalSearch(q, el) {
if(songs.length) {
const sec = mkSection('Titel');
const list = document.createElement('div'); list.className = 'song-list';
songs.forEach((so,i) => list.appendChild(makeSongItem(so, i, songs)));
songs.forEach((so,i) => list.appendChild(makeSongItem(so, i, songs, { singlePlay: true })));
sec.appendChild(list); el.appendChild(sec);
}
} catch(e) { el.innerHTML = `<p style="color:var(--danger)">${e.message}</p>`; }