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:
cucadmuh
2026-06-10 04:25:22 +03:00
committed by GitHub
parent ae9be74719
commit 707a41f615
8 changed files with 131 additions and 20 deletions
+7 -7
View File
@@ -7,9 +7,9 @@
*/
import type { QueueItemRef, Track } from './playerStoreTypes';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
const { savePlayQueueMock, playerState, progressSnapshot, isActiveServerReachableMock } = vi.hoisted(() => ({
const { savePlayQueueMock, playerState, progressSnapshot, isSubsonicServerReachableMock } = vi.hoisted(() => ({
savePlayQueueMock: vi.fn(async (_ids: string[], _currentId: string | undefined, _pos: number, _serverId: string) => undefined),
isActiveServerReachableMock: vi.fn(() => true),
isSubsonicServerReachableMock: vi.fn((_serverId: string) => true),
playerState: {
queueItems: [] as QueueItemRef[],
currentTrack: null as Track | null,
@@ -19,8 +19,8 @@ const { savePlayQueueMock, playerState, progressSnapshot, isActiveServerReachabl
}));
vi.mock('../api/subsonicPlayQueue', () => ({ savePlayQueue: savePlayQueueMock }));
vi.mock('../utils/network/activeServerReachability', () => ({
isActiveServerReachable: () => isActiveServerReachableMock(),
vi.mock('../utils/network/subsonicNetworkGuard', () => ({
isSubsonicServerReachable: (serverId: string) => isSubsonicServerReachableMock(serverId),
}));
vi.mock('../utils/playback/playbackServer', () => ({
getPlaybackServerId: () => 'srv-a',
@@ -52,7 +52,7 @@ function ref(id: string): QueueItemRef {
beforeEach(() => {
vi.useFakeTimers();
vi.setSystemTime(new Date('2026-05-12T12:00:00Z'));
isActiveServerReachableMock.mockReturnValue(true);
isSubsonicServerReachableMock.mockReturnValue(true);
savePlayQueueMock.mockClear();
savePlayQueueMock.mockResolvedValue(undefined);
playerState.queueItems = [];
@@ -69,8 +69,8 @@ afterEach(() => {
describe('syncQueueToServer (debounced)', () => {
const queue = [ref('a'), ref('b')];
it('skips sync while the active server is unreachable', () => {
isActiveServerReachableMock.mockReturnValue(false);
it('skips sync while the playback server is unreachable', () => {
isSubsonicServerReachableMock.mockReturnValue(false);
syncQueueToServer(queue, track('a'), 30);
vi.advanceTimersByTime(5000);
expect(savePlayQueueMock).not.toHaveBeenCalled();