diff --git a/src/lib/components/views/ArtistDetailView.svelte b/src/lib/components/views/ArtistDetailView.svelte index 934c982bd..c338c4f6b 100644 --- a/src/lib/components/views/ArtistDetailView.svelte +++ b/src/lib/components/views/ArtistDetailView.svelte @@ -309,7 +309,21 @@ void togglePlay(); } - function handleTrackPlay(track: Track, trackIndex?: number) { + function buildTopTracksQueue(tracks: Track[]) { + return tracks.map(t => ({ + id: t.id, + title: t.title, + artist: t.performer?.name || artist.name, + album: t.album?.title || '', + duration_secs: t.duration, + artwork_url: t.album?.image?.large || t.album?.image?.thumbnail || '', + hires: t.hires_streamable ?? false, + bit_depth: t.maximum_bit_depth ?? null, + sample_rate: t.maximum_sampling_rate ?? null, + })); + } + + async function handleTrackPlay(track: Track, trackIndex?: number) { // Create artist top tracks context if (topTracks.length > 0) { const trackIds = topTracks.map(t => t.id); @@ -325,6 +339,12 @@ index ); console.log(`[Artist] Context created: "${artist.name}" top tracks, ${trackIds.length} tracks, starting at ${index}`); + try { + const queueTracks = buildTopTracksQueue(topTracks); + await invoke('set_queue', { tracks: queueTracks, startIndex: index }); + } catch (err) { + console.error('Failed to set queue:', err); + } } } @@ -351,21 +371,8 @@ async function handlePlayAllTracks() { if (topTracks.length === 0 || !onTrackPlay) return; - const queueTracks = topTracks.map(t => ({ - id: t.id, - title: t.title, - artist: t.performer?.name || artist.name, - album: t.album?.title || '', - duration_secs: t.duration, - artwork_url: t.album?.image?.large || t.album?.image?.thumbnail || '', - hires: t.hires_streamable ?? false, - bit_depth: t.maximum_bit_depth ?? null, - sample_rate: t.maximum_sampling_rate ?? null, - })); - try { - await invoke('set_queue', { tracks: queueTracks, startIndex: 0 }); - handleTrackPlay(topTracks[0]); + await handleTrackPlay(topTracks[0], 0); } catch (err) { console.error('Failed to set queue:', err); } diff --git a/src/lib/components/views/FavoritesView.svelte b/src/lib/components/views/FavoritesView.svelte index 54b84edc9..18959b8b8 100644 --- a/src/lib/components/views/FavoritesView.svelte +++ b/src/lib/components/views/FavoritesView.svelte @@ -720,31 +720,8 @@ }; } - function handleTrackClick(track: FavoriteTrack, index: number) { - // Create favorites context - if (filteredTracks.length > 0) { - const trackIds = filteredTracks.map(t => t.id); - - setPlaybackContext( - 'favorites', - 'favorites', - 'Favorites', - 'qobuz', - trackIds, - index - ); - } - - // Play track - if (onTrackPlay) { - onTrackPlay(buildDisplayTrack(track, index)); - } - } - - async function handlePlayAllTracks() { - if (filteredTracks.length === 0 || !onTrackPlay) return; - - const queueTracks = filteredTracks.map(t => ({ + function buildFavoritesQueueTracks(tracks: FavoriteTrack[]) { + return tracks.map(t => ({ id: t.id, title: t.title, artist: t.performer?.name || 'Unknown Artist', @@ -755,10 +732,48 @@ bit_depth: t.maximum_bit_depth ?? null, sample_rate: t.maximum_sampling_rate ?? null, })); + } + + function setFavoritesContext(trackIds: number[], index: number) { + if (trackIds.length === 0) return; + setPlaybackContext( + 'favorites', + 'favorites', + 'Favorites', + 'qobuz', + trackIds, + index + ); + } + + async function setFavoritesQueue(startIndex: number) { + if (filteredTracks.length === 0) return; + const queueTracks = buildFavoritesQueueTracks(filteredTracks); + await invoke('set_queue', { tracks: queueTracks, startIndex }); + } + + async function handleTrackClick(track: FavoriteTrack, index: number) { + const trackIds = filteredTracks.map(t => t.id); + setFavoritesContext(trackIds, index); try { - await invoke('set_queue', { tracks: queueTracks, startIndex: 0 }); - handleTrackClick(filteredTracks[0], 0); + await setFavoritesQueue(index); + } catch (err) { + console.error('Failed to set queue:', err); + } + + if (onTrackPlay) { + onTrackPlay(buildDisplayTrack(track, index)); + } + } + + async function handlePlayAllTracks() { + if (filteredTracks.length === 0 || !onTrackPlay) return; + + try { + await setFavoritesQueue(0); + setFavoritesContext(filteredTracks.map(t => t.id), 0); + onTrackPlay(buildDisplayTrack(filteredTracks[0], 0)); } catch (err) { console.error('Failed to set queue:', err); } diff --git a/src/lib/components/views/HomeView.svelte b/src/lib/components/views/HomeView.svelte index 2f0da9566..c0173b958 100644 --- a/src/lib/components/views/HomeView.svelte +++ b/src/lib/components/views/HomeView.svelte @@ -335,7 +335,21 @@ return formatQuality(track.hires, track.bitDepth, track.samplingRate); } - function handleContinueTrackPlay(track: DisplayTrack, trackIndex: number) { + function buildContinueQueueTracks(tracks: DisplayTrack[]) { + return tracks.map(t => ({ + id: t.id, + title: t.title, + artist: t.artist || 'Unknown Artist', + album: t.album || '', + duration_secs: t.durationSeconds, + artwork_url: t.albumArt || '', + hires: t.hires ?? false, + bit_depth: t.bitDepth ?? null, + sample_rate: t.samplingRate ?? null, + })); + } + + async function handleContinueTrackPlay(track: DisplayTrack, trackIndex: number) { // Create continue listening context if (continueTracks.length > 0) { const trackIds = continueTracks.map(t => t.id); @@ -351,6 +365,15 @@ console.log(`[Home] Context created: Continue Listening, ${trackIds.length} tracks, starting at ${trackIndex}`); } + if (continueTracks.length > 0) { + try { + const queueTracks = buildContinueQueueTracks(continueTracks); + await invoke('set_queue', { tracks: queueTracks, startIndex: trackIndex }); + } catch (err) { + console.error('Failed to set queue:', err); + } + } + // Play track if (onTrackPlay) { onTrackPlay(track); diff --git a/src/lib/components/views/PlaylistDetailView.svelte b/src/lib/components/views/PlaylistDetailView.svelte index 8f2e9c832..e3069845f 100644 --- a/src/lib/components/views/PlaylistDetailView.svelte +++ b/src/lib/components/views/PlaylistDetailView.svelte @@ -544,29 +544,66 @@ return ''; } - function handleTrackClick(track: DisplayTrack) { + function buildQueueTracks(tracks: DisplayTrack[]) { + const queueTracks = tracks.map(t => ({ + id: t.isLocal ? Math.abs(t.id) : t.id, + title: t.title, + artist: t.artist || 'Unknown Artist', + album: t.album || playlist?.name || 'Playlist', + duration_secs: t.durationSeconds, + artwork_url: t.albumArt || getPlaylistImage(), + hires: t.hires ?? false, + bit_depth: t.bitDepth ?? null, + sample_rate: t.samplingRate != null ? (t.isLocal ? t.samplingRate * 1000 : t.samplingRate) : null, + is_local: t.isLocal ?? false, + })); + + const localIds = tracks + .filter(t => t.isLocal) + .map(t => Math.abs(t.id)); + + return { queueTracks, localIds }; + } + + async function setPlaylistQueue(startIndex: number) { + const allTracks = displayTracks; + if (allTracks.length === 0) return; + const { queueTracks, localIds } = buildQueueTracks(allTracks); + await invoke('set_queue', { tracks: queueTracks, startIndex }); + if (localIds.length > 0) { + onSetLocalQueue?.(localIds); + } + } + + async function handleTrackClick(track: DisplayTrack, trackIndex: number) { // Create playlist context before playing if (playlist) { const trackIds = displayTracks .filter(t => !t.isLocal) // Only Qobuz tracks in context .map(t => t.id); - const trackIndex = trackIds.indexOf(track.id); + const contextIndex = trackIds.indexOf(track.id); - if (trackIndex >= 0 && trackIds.length > 0) { + if (contextIndex >= 0 && trackIds.length > 0) { setPlaybackContext( 'playlist', playlist.id.toString(), playlist.name, 'qobuz', trackIds, - trackIndex + contextIndex ); - console.log(`[Playlist] Context created: "${playlist.name}", ${trackIds.length} tracks, starting at ${trackIndex}`); + console.log(`[Playlist] Context created: "${playlist.name}", ${trackIds.length} tracks, starting at ${contextIndex}`); } } // Handle playback + try { + await setPlaylistQueue(trackIndex); + } catch (err) { + console.error('Failed to set queue:', err); + } + if (track.isLocal && track.localTrackId) { // Handle local track play const localTrack = localTracksMap.get(track.localTrackId); @@ -628,32 +665,8 @@ const allTracks = displayTracks; if (allTracks.length === 0) return; - // Build queue tracks, using absolute ID for local tracks - const queueTracks = allTracks.map(t => ({ - id: t.isLocal ? Math.abs(t.id) : t.id, - title: t.title, - artist: t.artist || 'Unknown Artist', - album: t.album || playlist?.name || 'Playlist', - duration_secs: t.durationSeconds, - artwork_url: t.albumArt || getPlaylistImage(), - hires: t.hires ?? false, - bit_depth: t.bitDepth ?? null, - sample_rate: t.samplingRate != null ? (t.isLocal ? t.samplingRate * 1000 : t.samplingRate) : null, - is_local: t.isLocal ?? false, - })); - - // Collect local track IDs (original positive IDs) - const localIds = allTracks - .filter(t => t.isLocal) - .map(t => Math.abs(t.id)); - try { - await invoke('set_queue', { tracks: queueTracks, startIndex: 0 }); - - // Tell parent about local tracks in queue - if (localIds.length > 0) { - onSetLocalQueue?.(localIds); - } + await setPlaylistQueue(0); // Play first track (handle local vs Qobuz) const firstTrack = allTracks[0]; @@ -991,11 +1004,11 @@ hideDownload={track.isLocal} downloadStatus={downloadInfo.status} downloadProgress={downloadInfo.progress} - onPlay={available ? () => handleTrackClick(track) : undefined} + onPlay={available ? () => handleTrackClick(track, idx) : undefined} onDownload={available && !track.isLocal && onTrackDownload ? () => onTrackDownload(track) : undefined} onRemoveDownload={available && !track.isLocal && onTrackRemoveDownload ? () => onTrackRemoveDownload(track.id) : undefined} menuActions={available ? { - onPlayNow: () => handleTrackClick(track), + onPlayNow: () => handleTrackClick(track, idx), onPlayNext: track.isLocal ? () => handleTrackPlayNext(track) : (onTrackPlayNext ? () => onTrackPlayNext(track) : undefined), onPlayLater: track.isLocal ? () => handleTrackPlayLater(track) : (onTrackPlayLater ? () => onTrackPlayLater(track) : undefined), onAddToPlaylist: !track.isLocal && onTrackAddToPlaylist ? () => onTrackAddToPlaylist(track.id) : undefined, diff --git a/src/lib/components/views/SearchView.svelte b/src/lib/components/views/SearchView.svelte index 6bb7b5f59..12222a815 100644 --- a/src/lib/components/views/SearchView.svelte +++ b/src/lib/components/views/SearchView.svelte @@ -381,7 +381,21 @@ return track.album?.image?.large || track.album?.image?.thumbnail || track.album?.image?.small || ''; } - function handleSearchTrackPlay(track: Track, trackIndex: number) { + function buildSearchQueueTracks(tracks: Track[]) { + return tracks.map(t => ({ + id: t.id, + title: t.title, + artist: t.performer?.name || 'Unknown Artist', + album: t.album?.title || '', + duration_secs: t.duration, + artwork_url: t.album?.image?.large || t.album?.image?.thumbnail || t.album?.image?.small || '', + hires: t.hires_streamable ?? false, + bit_depth: t.maximum_bit_depth ?? null, + sample_rate: t.maximum_sampling_rate ?? null, + })); + } + + async function handleSearchTrackPlay(track: Track, trackIndex: number) { // Create search results context if (trackResults && trackResults.items.length > 0) { const trackIds = trackResults.items.map(t => t.id); @@ -397,6 +411,15 @@ console.log(`[Search] Context created: "${currentQuery}", ${trackIds.length} tracks, starting at ${trackIndex}`); } + if (trackResults && trackResults.items.length > 0) { + try { + const queueTracks = buildSearchQueueTracks(trackResults.items); + await invoke('set_queue', { tracks: queueTracks, startIndex: trackIndex }); + } catch (err) { + console.error('Failed to set queue:', err); + } + } + // Play track if (onTrackPlay) { onTrackPlay(track); diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 7528b63f4..430a76006 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -35,7 +35,8 @@ } from '$lib/stores/playbackContextStore'; import { initPlaybackPreferences, - getCachedPreferences + getCachedPreferences, + isAutoplayEnabled } from '$lib/stores/playbackPreferencesStore'; // UI state management @@ -1853,6 +1854,10 @@ // Set up track ended callback for auto-advance setOnTrackEnded(async () => { + if (!isAutoplayEnabled()) { + setQueueEnded(true); + return; + } const nextTrackResult = await nextTrack(); if (nextTrackResult) { await playQueueTrack(nextTrackResult);