mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 07:15:47 +00:00
fix(playback): cross-server browse, Lucky Mix, and Now Playing (#768)
* fix(playback): keep browsed server when queue plays elsewhere Lucky Mix on a non-playback server now clears the old queue and pins the active server before building. Now Playing metadata uses apiForServer against the queue server instead of forcing ensurePlaybackServerActive on every activeServerId change. * docs: note PR #768 in CHANGELOG and settings credits * fix(now-playing): gate AudioMuse similar artists on playback server Match getArtistInfoForServer credentials: when browsing another server while the queue plays elsewhere, similar-artist count follows the queue server's AudioMuse flag, not the browsed server.
This commit is contained in:
@@ -11,6 +11,10 @@ import { usePlayerStore } from '../../store/playerStore';
|
||||
import { useLuckyMixStore } from '../../store/luckyMixStore';
|
||||
import { isLuckyMixAvailable } from '../../hooks/useLuckyMixAvailable';
|
||||
import { showToast } from '../ui/toast';
|
||||
import {
|
||||
playbackServerDiffersFromActive,
|
||||
prepareActiveServerForNewMix,
|
||||
} from '../playback/playbackServer';
|
||||
import {
|
||||
filterSongsForLuckyMixRatings,
|
||||
filterTopArtistsForMixRatings,
|
||||
@@ -74,6 +78,7 @@ export async function buildAndPlayLuckyMix(): Promise<void> {
|
||||
showLuckyMixMenu: auth.showLuckyMixMenu,
|
||||
libraryFilter: activeServerId ? (auth.musicLibraryFilterByServer[activeServerId] ?? 'all') : 'all',
|
||||
mixRatingFilter: mixRatingCfg,
|
||||
crossServerPlayback: playbackServerDiffersFromActive(),
|
||||
});
|
||||
if (!available) {
|
||||
logStep('abort_unavailable');
|
||||
@@ -96,9 +101,16 @@ export async function buildAndPlayLuckyMix(): Promise<void> {
|
||||
|
||||
let unsubPlayer: (() => void) | null = null;
|
||||
try {
|
||||
// Drop the old "upcoming" tail immediately so the queue UI does not show stale
|
||||
// next tracks while the mix is still building (first playTrack may be delayed).
|
||||
usePlayerStore.getState().pruneUpcomingToCurrent(true);
|
||||
// Browsed server ≠ queue server: stop A's stream so Now Playing does not call
|
||||
// ensurePlaybackServerActive() and revert the UI mid-build.
|
||||
if (playbackServerDiffersFromActive()) {
|
||||
prepareActiveServerForNewMix();
|
||||
logStep('cross_server_handoff', { activeServerId });
|
||||
} else {
|
||||
// Drop the old "upcoming" tail so the queue UI does not show stale next
|
||||
// tracks while the mix is still building (first playTrack may be delayed).
|
||||
usePlayerStore.getState().pruneUpcomingToCurrent(true);
|
||||
}
|
||||
|
||||
lucky.start();
|
||||
let startedPlayback = false;
|
||||
|
||||
@@ -8,8 +8,10 @@ import {
|
||||
getPlaybackServerId,
|
||||
playbackCoverArtForId,
|
||||
playbackServerDiffersFromActive,
|
||||
prepareActiveServerForNewMix,
|
||||
shouldBindQueueServerForPlay,
|
||||
} from './playbackServer';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { vi } from 'vitest';
|
||||
|
||||
vi.mock('../server/switchActiveServer', () => ({
|
||||
@@ -58,6 +60,24 @@ describe('playbackServer', () => {
|
||||
expect(playbackServerDiffersFromActive()).toBe(false);
|
||||
});
|
||||
|
||||
it('prepareActiveServerForNewMix clears queue and pins browsed server', async () => {
|
||||
vi.mocked(invoke).mockResolvedValue(undefined);
|
||||
useAuthStore.setState({ activeServerId: 'b' });
|
||||
prepareActiveServerForNewMix();
|
||||
const s = usePlayerStore.getState();
|
||||
expect(s.queue).toEqual([]);
|
||||
expect(s.currentTrack).toBeNull();
|
||||
expect(s.queueServerId).toBe('b');
|
||||
expect(playbackServerDiffersFromActive()).toBe(false);
|
||||
});
|
||||
|
||||
it('prepareActiveServerForNewMix is a no-op when queue already matches active', () => {
|
||||
useAuthStore.setState({ activeServerId: 'a' });
|
||||
prepareActiveServerForNewMix();
|
||||
expect(usePlayerStore.getState().queue).toHaveLength(1);
|
||||
expect(usePlayerStore.getState().queueServerId).toBe('a');
|
||||
});
|
||||
|
||||
it('ensurePlaybackServerActive calls switch when servers differ', async () => {
|
||||
const { switchActiveServer } = await import('../server/switchActiveServer');
|
||||
useAuthStore.setState({ activeServerId: 'b' });
|
||||
|
||||
@@ -34,6 +34,16 @@ export function playbackServerDiffersFromActive(): boolean {
|
||||
return !!activeSid && queueServerId !== activeSid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop playback owned by another server so a new mix on the browsed server
|
||||
* can replace the queue (Lucky Mix / similar flows after ConnectionIndicator switch).
|
||||
*/
|
||||
export function prepareActiveServerForNewMix(): void {
|
||||
if (!playbackServerDiffersFromActive()) return;
|
||||
usePlayerStore.getState().clearQueue();
|
||||
bindQueueServerForPlayback();
|
||||
}
|
||||
|
||||
/** Switch the browsed server to the queue server when they differ (e.g. artist/album links). */
|
||||
export async function ensurePlaybackServerActive(): Promise<boolean> {
|
||||
if (!playbackServerDiffersFromActive()) return true;
|
||||
|
||||
Reference in New Issue
Block a user