mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-21 23:05:46 +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:
@@ -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();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { savePlayQueue } from '../api/subsonicPlayQueue';
|
||||
import type { QueueItemRef, Track } from './playerStoreTypes';
|
||||
import { isActiveServerReachable } from '../utils/network/activeServerReachability';
|
||||
import { isSubsonicServerReachable } from '../utils/network/subsonicNetworkGuard';
|
||||
import { getPlaybackServerId } from '../utils/playback/playbackServer';
|
||||
import { getPlaybackProgressSnapshot } from './playbackProgress';
|
||||
import { usePlayerStore } from './playerStore';
|
||||
@@ -27,12 +27,17 @@ const QUEUE_ID_LIMIT = 1000;
|
||||
let syncTimeout: ReturnType<typeof setTimeout> | null = null;
|
||||
let lastQueueHeartbeatAt = 0;
|
||||
|
||||
function isPlaybackServerReachable(): boolean {
|
||||
const serverId = getPlaybackServerId();
|
||||
return serverId ? isSubsonicServerReachable(serverId) : false;
|
||||
}
|
||||
|
||||
export function syncQueueToServer(queue: QueueItemRef[], currentTrack: Track | null, currentTime: number): void {
|
||||
if (!isActiveServerReachable()) return;
|
||||
if (!isPlaybackServerReachable()) return;
|
||||
if (syncTimeout) clearTimeout(syncTimeout);
|
||||
syncTimeout = setTimeout(() => {
|
||||
syncTimeout = null;
|
||||
if (!isActiveServerReachable()) return;
|
||||
if (!isPlaybackServerReachable()) return;
|
||||
const ids = queue.slice(0, QUEUE_ID_LIMIT).map(r => r.trackId);
|
||||
const pos = Math.floor(currentTime * 1000);
|
||||
const serverId = getPlaybackServerId();
|
||||
@@ -47,7 +52,7 @@ export function flushQueueSyncToServer(queue: QueueItemRef[], currentTrack: Trac
|
||||
clearTimeout(syncTimeout);
|
||||
syncTimeout = null;
|
||||
}
|
||||
if (!isActiveServerReachable()) return Promise.resolve();
|
||||
if (!isPlaybackServerReachable()) return Promise.resolve();
|
||||
if (!currentTrack || queue.length === 0) return Promise.resolve();
|
||||
lastQueueHeartbeatAt = Date.now();
|
||||
const ids = queue.slice(0, QUEUE_ID_LIMIT).map(r => r.trackId);
|
||||
|
||||
Reference in New Issue
Block a user