diff --git a/src-tauri/src/audio.rs b/src-tauri/src/audio.rs index ffd9f792..37ebb53c 100644 --- a/src-tauri/src/audio.rs +++ b/src-tauri/src/audio.rs @@ -5412,12 +5412,30 @@ pub async fn audio_preview_play( #[tauri::command] pub fn audio_preview_stop(app: AppHandle, state: State<'_, AudioEngine>) { + preview_stop_inner(&app, &state, true); +} + +/// Like `audio_preview_stop` but leaves the main sink paused even if it had +/// been paused by `preview_pause_main`. Used by the player-bar Stop button so +/// "stop everything" actually goes silent — without this the engine would +/// auto-resume main playback the moment the preview ends and the user perceives +/// the click as having no effect. +#[tauri::command] +pub fn audio_preview_stop_silent(app: AppHandle, state: State<'_, AudioEngine>) { + preview_stop_inner(&app, &state, false); +} + +fn preview_stop_inner(app: &AppHandle, state: &AudioEngine, resume_main: bool) { state.preview_gen.fetch_add(1, Ordering::SeqCst); let sink = state.preview_sink.lock().unwrap().take(); let id = state.preview_song_id.lock().unwrap().take(); if let Some(s) = sink { s.stop(); } - preview_resume_main(&state); + if resume_main { + preview_resume_main(state); + } else { + state.preview_main_resume.store(false, Ordering::Release); + } if let Some(id) = id { app.emit("audio:preview-end", PreviewEndPayload { diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 43bd58b7..0f42c993 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -4599,6 +4599,7 @@ pub fn run() { audio::audio_play_radio, audio::audio_preview_play, audio::audio_preview_stop, + audio::audio_preview_stop_silent, audio::audio_set_crossfade, audio::audio_set_gapless, audio::audio_set_normalization, diff --git a/src/components/AlbumTrackList.tsx b/src/components/AlbumTrackList.tsx index eb32d088..c454b826 100644 --- a/src/components/AlbumTrackList.tsx +++ b/src/components/AlbumTrackList.tsx @@ -160,7 +160,7 @@ const TrackRow = React.memo(function TrackRow({ className={`playlist-suggestion-preview-btn${isPreviewing ? ' is-previewing' : ''}`} onClick={e => { e.stopPropagation(); - usePreviewStore.getState().startPreview({ id: song.id, duration: song.duration }, 'albums'); + usePreviewStore.getState().startPreview({ id: song.id, title: song.title, artist: song.artist, coverArt: song.coverArt, duration: song.duration }, 'albums'); }} data-tooltip={isPreviewing ? t('playlists.previewStop') : t('playlists.preview')} aria-label={isPreviewing ? t('playlists.previewStop') : t('playlists.preview')} diff --git a/src/components/PlayerBar.tsx b/src/components/PlayerBar.tsx index a1e097bd..0a21307e 100644 --- a/src/components/PlayerBar.tsx +++ b/src/components/PlayerBar.tsx @@ -25,6 +25,7 @@ import { usePlaybackDelayPress } from '../hooks/usePlaybackDelayPress'; import PlaybackDelayModal from './PlaybackDelayModal'; import PlaybackScheduleBadge from './PlaybackScheduleBadge'; import { usePlaybackScheduleRemaining } from '../utils/playbackScheduleFormat'; +import { usePreviewStore } from '../store/previewStore'; function formatTime(seconds: number): string { if (!seconds || isNaN(seconds)) return '0:00'; @@ -144,6 +145,8 @@ export default function PlayerBar() { const transportAnchorRef = useRef(null); const playSlotRef = useRef(null); const scheduleRemaining = usePlaybackScheduleRemaining(); + const isPreviewing = usePreviewStore(s => s.previewingId !== null); + const previewingTrack = usePreviewStore(s => s.previewingTrack); const isRadio = !!currentRadio; @@ -176,8 +179,15 @@ export default function PlayerBar() { [currentRadio?.coverArt, currentRadio?.id] ); const radioCoverKey = currentRadio?.coverArt ? coverArtCacheKey(`ra-${currentRadio.id}`, 128) : ''; - const coverSrc = useMemo(() => currentTrack?.coverArt ? buildCoverArtUrl(currentTrack.coverArt, 128) : '', [currentTrack?.coverArt]); - const coverKey = currentTrack?.coverArt ? coverArtCacheKey(currentTrack.coverArt, 128) : ''; + // Preview takes visual priority over the queued track in the player-bar info + // cell, but only when not in radio mode (radio has its own meta layout). + const showPreviewMeta = isPreviewing && !isRadio && previewingTrack !== null; + const displayCoverArt = showPreviewMeta ? previewingTrack!.coverArt : currentTrack?.coverArt; + const displayTitle = showPreviewMeta ? previewingTrack!.title : (currentTrack?.title ?? t('player.noTitle')); + const displayArtist = showPreviewMeta ? previewingTrack!.artist : (currentTrack?.artist ?? '—'); + + const coverSrc = useMemo(() => displayCoverArt ? buildCoverArtUrl(displayCoverArt, 128) : '', [displayCoverArt]); + const coverKey = displayCoverArt ? coverArtCacheKey(displayCoverArt, 128) : ''; const handleVolume = useCallback((e: React.ChangeEvent) => { setVolume(parseFloat(e.target.value)); @@ -196,7 +206,7 @@ export default function PlayerBar() { const playerBarContent = ( <>