mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 07:15:47 +00:00
fix(scrobble): Navidrome Now Playing with local playback and mixed-server queue (#1055)
* fix(scrobble): report Now Playing on playback server with local bytes Navidrome presence and play-count scrobbles no longer skip when audio plays from hot cache, offline pins, or favorites-auto, and reachability follows the queue/playback server instead of the browsed active server. * docs: changelog and credits for PR #1055
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import { scrobbleSong } from './subsonicScrobble';
|
||||
import { reportNowPlaying, scrobbleSong } from './subsonicScrobble';
|
||||
import { shouldAttemptSubsonicForServer } from '../utils/network/subsonicNetworkGuard';
|
||||
|
||||
const { apiForServerMock } = vi.hoisted(() => ({
|
||||
apiForServerMock: vi.fn(async () => ({})),
|
||||
@@ -12,12 +13,13 @@ vi.mock('./subsonicClient', () => ({
|
||||
apiForServer: apiForServerMock,
|
||||
}));
|
||||
vi.mock('../utils/network/subsonicNetworkGuard', () => ({
|
||||
shouldAttemptSubsonicForServer: () => true,
|
||||
shouldAttemptSubsonicForServer: vi.fn(() => true),
|
||||
}));
|
||||
|
||||
describe('subsonicScrobble', () => {
|
||||
beforeEach(() => {
|
||||
apiForServerMock.mockClear();
|
||||
vi.mocked(shouldAttemptSubsonicForServer).mockImplementation(() => true);
|
||||
useAuthStore.setState({
|
||||
servers: [
|
||||
{ id: 'a', name: 'A', url: 'http://a.test', username: 'u', password: 'p' },
|
||||
@@ -41,4 +43,29 @@ describe('subsonicScrobble', () => {
|
||||
expect.objectContaining({ id: 't1', submission: true, time: 1_700_000_000_000 }),
|
||||
);
|
||||
});
|
||||
|
||||
it('reportNowPlaying and scrobbleSong use the presence guard without trackId', async () => {
|
||||
vi.mocked(shouldAttemptSubsonicForServer).mockImplementation(
|
||||
(_serverId: string, trackId?: string) => trackId === undefined,
|
||||
);
|
||||
|
||||
await reportNowPlaying('t-local', 'a');
|
||||
await scrobbleSong('t-local', 1_700_000_000_000, 'a');
|
||||
|
||||
expect(shouldAttemptSubsonicForServer).toHaveBeenCalledWith('a');
|
||||
expect(shouldAttemptSubsonicForServer).not.toHaveBeenCalledWith('a', expect.anything());
|
||||
expect(apiForServerMock).toHaveBeenCalledTimes(2);
|
||||
expect(apiForServerMock).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'a',
|
||||
'scrobble.view',
|
||||
expect.objectContaining({ id: 't-local', submission: false }),
|
||||
);
|
||||
expect(apiForServerMock).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
'a',
|
||||
'scrobble.view',
|
||||
expect.objectContaining({ id: 't-local', submission: true }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,7 +9,9 @@ async function scrobbleOnServer(
|
||||
submission: boolean,
|
||||
time?: number,
|
||||
): Promise<void> {
|
||||
if (!shouldAttemptSubsonicForServer(serverId, id)) return;
|
||||
// Presence / play-count updates are not playback-byte fetches — omit trackId so
|
||||
// hot cache, offline library, and favorites-auto do not suppress Navidrome calls.
|
||||
if (!shouldAttemptSubsonicForServer(serverId)) return;
|
||||
const params: Record<string, unknown> = { id, submission };
|
||||
if (time !== undefined) params.time = time;
|
||||
await apiForServer(serverId, 'scrobble.view', params);
|
||||
|
||||
Reference in New Issue
Block a user