fix(playback): pin queue playback to source server when browsing another library (#717)

* fix(playback): pin queue streams, cover art, and library links to queue server

When the active server changes while a queue from another server is playing,
keep streams and UI on queueServerId; switch back for artist/album links and
queue or player-bar context menus.

* fix(playback): switch to queue server when opening Now Playing

Ensure active server matches queueServerId before Subsonic fetches on the
Now Playing page, mobile player route, and queue info panel; scope caches
by server id.

* docs(credits): mention Now Playing in PR #717 contribution line

* fix(playback): route scrobble and queue sync to queue server

Address PR review: apiForServer for scrobble/now-playing/savePlayQueue,
clear queueServerId on server removal, mini-player queueServerId sync,
block cross-server enqueue with toast, and regression tests.
This commit is contained in:
cucadmuh
2026-05-15 16:08:41 +03:00
committed by GitHub
parent a9b50b9244
commit 45e0e1206f
58 changed files with 701 additions and 178 deletions
+4 -9
View File
@@ -1,8 +1,9 @@
import { buildCoverArtUrl, coverArtCacheKey } from '../api/subsonicStreamUrl';
import { usePlaybackCoverArt } from '../hooks/usePlaybackCoverArt';
import { useEffect, useMemo, useRef, useState } from 'react';
import { emit } from '@tauri-apps/api/event';
import { invoke } from '@tauri-apps/api/core';
import { useTranslation } from 'react-i18next';
import { usePlayerStore } from '../store/playerStore';
import { registerQueueDragHitTest } from '../contexts/DragDropContext';
import MiniContextMenu from './MiniContextMenu';
import type { MiniSyncPayload, MiniControlAction, MiniTrackInfo } from '../utils/miniPlayerBridge';
@@ -63,6 +64,7 @@ export default function MiniPlayer() {
useMiniSync({
onSync: (payload) => {
setState(payload);
usePlayerStore.setState({ queueServerId: payload.queueServerId ?? null });
if (payload.track?.duration) setDuration(payload.track.duration);
if (typeof payload.volume === 'number') setVolumeState(payload.volume);
},
@@ -138,14 +140,7 @@ export default function MiniPlayer() {
}, [queueOpen, state.queueIndex]);
const { track, isPlaying } = state;
const miniCoverSrc = useMemo(
() => (track?.coverArt ? buildCoverArtUrl(track.coverArt, 300) : ''),
[track?.coverArt],
);
const miniCoverKey = useMemo(
() => (track?.coverArt ? coverArtCacheKey(track.coverArt, 300) : ''),
[track?.coverArt],
);
const { src: miniCoverSrc, cacheKey: miniCoverKey } = usePlaybackCoverArt(track?.coverArt, 300);
const progress = duration > 0 ? Math.min(100, (currentTime / duration) * 100) : 0;
return (