diff --git a/src-tauri/src/playback_mode/mod.rs b/src-tauri/src/playback_mode/mod.rs
index 5602f1b..056b250 100644
--- a/src-tauri/src/playback_mode/mod.rs
+++ b/src-tauri/src/playback_mode/mod.rs
@@ -215,8 +215,16 @@ impl PlaybackModeManager {
log::info!("[PlaybackMode] transfer_to_remote_inner ENTERED");
debug!("[PlaybackMode] transfer_to_remote_inner: session_id={}", session_id);
+ // If we're already controlling a remote session, that *old* session — not
+ // the idle local player — is the source of truth for the current track and
+ // position. Capture it so we can resume there and stop it afterwards.
+ let previous_remote_session = match self.get_mode() {
+ PlaybackMode::Remote { session_id: prev } if prev != session_id => Some(prev),
+ _ => None,
+ };
+
// Get current player state and queue context
- let (queue_ids, current_index, position_seconds, queue_context) = {
+ let (queue_ids, mut current_index, mut position_seconds, queue_context) = {
log::info!("[PlaybackMode] Acquiring player controller lock...");
debug!("[PlaybackMode] Acquiring player controller lock...");
let player = self.player_controller.lock().await;
@@ -313,6 +321,46 @@ impl PlaybackModeManager {
}
};
+ // Remote -> remote switch: take the current track and position from the
+ // session we're leaving, since the local player is idle and reports 0.
+ if let Some(ref prev_session_id) = previous_remote_session {
+ log::info!(
+ "[PlaybackMode] Remote->remote switch; reading state from previous session {}",
+ prev_session_id
+ );
+ match client.get_session(prev_session_id).await {
+ Ok(Some(session)) => {
+ // Resume at the previous session's position.
+ if let Some(ticks) = session.play_state.as_ref().and_then(|ps| ps.position_ticks) {
+ position_seconds = ticks as f64 / TICKS_PER_SECOND;
+ log::info!(
+ "[PlaybackMode] Using previous remote position: {:.2}s",
+ position_seconds
+ );
+ }
+ // Resume on whichever track the previous session reached.
+ if let Some(now_id) = session.now_playing_item.as_ref().and_then(|i| i.id.as_deref()) {
+ if let Some(idx) = queue_ids.iter().position(|id| id == now_id) {
+ log::info!(
+ "[PlaybackMode] Previous session is on track {} (queue index {})",
+ now_id,
+ idx
+ );
+ current_index = idx;
+ } else {
+ log::warn!(
+ "[PlaybackMode] Previous session's track {} not found in queue; keeping index {}",
+ now_id,
+ current_index
+ );
+ }
+ }
+ }
+ Ok(None) => log::warn!("[PlaybackMode] Previous remote session not found while reading state"),
+ Err(e) => log::warn!("[PlaybackMode] Failed to read previous remote session: {}", e),
+ }
+ }
+
// Calculate position in ticks (from the live position read above)
let start_position_ticks = start_position_ticks_from_seconds(position_seconds);
@@ -446,6 +494,16 @@ impl PlaybackModeManager {
}
}
+ // Remote -> remote switch: stop the session we just left so we don't end
+ // up with two devices playing at once. Do this only after the new session
+ // is confirmed playing, so a failure here doesn't leave us with silence.
+ if let Some(prev_session_id) = previous_remote_session {
+ log::info!("[PlaybackMode] Stopping previous remote session {}", prev_session_id);
+ if let Err(e) = client.send_session_command(prev_session_id, "Stop").await {
+ log::warn!("[PlaybackMode] Failed to stop previous remote session: {}", e);
+ }
+ }
+
// Stop local playback (queue should remain intact for remote session)
log::info!("[PlaybackMode] Stopping local playback - queue should NOT be cleared");
{
diff --git a/src/lib/components/library/TrackList.test.ts b/src/lib/components/library/TrackList.test.ts
index 55e1f4c..4e41399 100644
--- a/src/lib/components/library/TrackList.test.ts
+++ b/src/lib/components/library/TrackList.test.ts
@@ -28,6 +28,7 @@ vi.mock("$lib/stores/queue", () => ({
setQueue: vi.fn(),
addToQueue: vi.fn(),
},
+ currentQueueItem: { subscribe: vi.fn((fn: any) => { fn(null); return () => {}; }) },
}));
vi.mock("./DownloadButton.svelte", () => ({
diff --git a/src/lib/stores/player.ts b/src/lib/stores/player.ts
index 4bd21a7..d7db36b 100644
--- a/src/lib/stores/player.ts
+++ b/src/lib/stores/player.ts
@@ -13,6 +13,7 @@ import type { MediaItem, ItemType } from "$lib/api/types";
import type { NowPlayingItem } from "$lib/api/bindings";
import { isRemoteMode } from "./playbackMode";
import { selectedSession } from "./sessions";
+import { currentQueueItem } from "./queue";
import { ticksToSeconds } from "$lib/utils/playbackUnits";
// Merged media item from backend (matches Rust MergedMediaItem)
@@ -257,21 +258,24 @@ export const mergedVolume = derived(
* AND current media is audio (not video: Movie or Episode)
*/
export const shouldShowAudioMiniPlayer = derived(
- [player, currentMedia, isRemoteMode, selectedSession],
- ([$player, $media, $isRemote, $session]) => {
+ [player, currentMedia, currentQueueItem, isRemoteMode, selectedSession],
+ ([$player, $media, $queueItem, $isRemote, $session]) => {
// In remote mode, show if the remote session has a now-playing item
if ($isRemote && $session?.nowPlayingItem) {
return true;
}
- // Local mode: only show when actively playing or paused
+ // Local mode: hide only when there is genuinely nothing loaded
+ // (idle/stopped/error). Keep showing through loading/seeking transitions
+ // so the mini player doesn't blink out when advancing between tracks.
const state = $player.state;
- if (state.kind !== "playing" && state.kind !== "paused") {
+ if (state.kind === "idle" || state.kind === "error") {
return false;
}
- // Don't show for video content
- const mediaType = $media?.type;
+ // Determine media type from the player state, falling back to the queue
+ // item (the player store can momentarily lack media during transitions).
+ const mediaType = $media?.type ?? $queueItem?.type;
if (mediaType === "Movie" || mediaType === "Episode") {
return false;
}
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte
index 7e545ce..bcd5842 100644
--- a/src/routes/+layout.svelte
+++ b/src/routes/+layout.svelte
@@ -136,10 +136,10 @@
{/if}
-
-
+
+
- {#if !$page.url.pathname.startsWith('/player/') && !$page.url.pathname.startsWith('/login')}
+ {#if !$page.url.pathname.startsWith('/player/') && !$page.url.pathname.startsWith('/login') && !$page.url.pathname.startsWith('/settings')}
{#if $isAndroid || !$page.url.pathname.startsWith('/library')}