mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-21 23:05:46 +00:00
fix(queue): suspend idle pull only on user queue edits (#1136)
This commit is contained in:
@@ -134,6 +134,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
* The header connection probe now retries a failed ping twice (2 s apart) before marking the server unreachable, so a single dropped packet on an otherwise fine link no longer flips the LED to disconnected.
|
* The header connection probe now retries a failed ping twice (2 s apart) before marking the server unreachable, so a single dropped packet on an otherwise fine link no longer flips the LED to disconnected.
|
||||||
|
|
||||||
|
### Yellow sync LED during normal playback
|
||||||
|
|
||||||
|
**By [@cucadmuh](https://github.com/cucadmuh), PR [#1136](https://github.com/Psychotoxical/psysonic/pull/1136)**
|
||||||
|
|
||||||
|
* Track-advance queue pushes no longer suspend idle auto-pull, so the connection LED does not flash yellow on every song change. Yellow sync still appears after a local queue edit while paused; it clears while audio is playing.
|
||||||
|
|
||||||
|
|
||||||
## [1.48.1] - 2026-06-15
|
## [1.48.1] - 2026-06-15
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ export function usePlayQueueSyncLedState(status: ConnectionStatus) {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const activeServerId = useAuthStore(s => s.activeServerId);
|
const activeServerId = useAuthStore(s => s.activeServerId);
|
||||||
const orbitRole = useOrbitStore(s => s.role);
|
const orbitRole = useOrbitStore(s => s.role);
|
||||||
|
const isPlaying = usePlayerStore(s => s.isPlaying);
|
||||||
const currentRadio = usePlayerStore(s => s.currentRadio);
|
const currentRadio = usePlayerStore(s => s.currentRadio);
|
||||||
const [pullInFlight, setPullInFlight] = useState(false);
|
const [pullInFlight, setPullInFlight] = useState(false);
|
||||||
const idlePullSuspended = useSyncExternalStore(
|
const idlePullSuspended = useSyncExternalStore(
|
||||||
@@ -40,7 +41,7 @@ export function usePlayQueueSyncLedState(status: ConnectionStatus) {
|
|||||||
}, [activeServerId, playbackServerId]);
|
}, [activeServerId, playbackServerId]);
|
||||||
|
|
||||||
const autoSyncContext = canAutoIdlePlayQueuePull(status, orbitRole);
|
const autoSyncContext = canAutoIdlePlayQueuePull(status, orbitRole);
|
||||||
const localQueueSyncPaused = autoSyncContext && idlePullSuspended;
|
const localQueueSyncPaused = autoSyncContext && idlePullSuspended && !isPlaying;
|
||||||
|
|
||||||
const needsQueuePull = status === 'connected'
|
const needsQueuePull = status === 'connected'
|
||||||
&& Boolean(activeServerId)
|
&& Boolean(activeServerId)
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import { refreshLoudnessForTrack } from './loudnessRefresh';
|
|||||||
import { refreshWaveformForTrack } from './waveformRefresh';
|
import { refreshWaveformForTrack } from './waveformRefresh';
|
||||||
import { stopRadio } from './radioPlayer';
|
import { stopRadio } from './radioPlayer';
|
||||||
import { clearAllPlaybackScheduleTimers } from './scheduleTimers';
|
import { clearAllPlaybackScheduleTimers } from './scheduleTimers';
|
||||||
import { syncQueueToServer } from './queueSync';
|
import { syncUserQueueMutationToServer } from './queueSync';
|
||||||
|
|
||||||
type SetState = (
|
type SetState = (
|
||||||
partial: Partial<PlayerState> | ((state: PlayerState) => Partial<PlayerState>),
|
partial: Partial<PlayerState> | ((state: PlayerState) => Partial<PlayerState>),
|
||||||
@@ -209,7 +209,7 @@ export function applyQueueHistorySnapshot(
|
|||||||
if (!nextTrack) {
|
if (!nextTrack) {
|
||||||
invoke('audio_stop').catch(console.error);
|
invoke('audio_stop').catch(console.error);
|
||||||
setIsAudioPaused(false);
|
setIsAudioPaused(false);
|
||||||
syncQueueToServer(nextItems, null, 0);
|
syncUserQueueMutationToServer(nextItems, null, 0);
|
||||||
if (typeof snap.queueListScrollTop === 'number' && Number.isFinite(snap.queueListScrollTop)) {
|
if (typeof snap.queueListScrollTop === 'number' && Number.isFinite(snap.queueListScrollTop)) {
|
||||||
setPendingQueueListScrollTop(Math.max(0, snap.queueListScrollTop));
|
setPendingQueueListScrollTop(Math.max(0, snap.queueListScrollTop));
|
||||||
}
|
}
|
||||||
@@ -235,6 +235,6 @@ export function applyQueueHistorySnapshot(
|
|||||||
if (typeof snap.queueListScrollTop === 'number' && Number.isFinite(snap.queueListScrollTop)) {
|
if (typeof snap.queueListScrollTop === 'number' && Number.isFinite(snap.queueListScrollTop)) {
|
||||||
setPendingQueueListScrollTop(Math.max(0, snap.queueListScrollTop));
|
setPendingQueueListScrollTop(Math.max(0, snap.queueListScrollTop));
|
||||||
}
|
}
|
||||||
syncQueueToServer(nextItems, nextTrack, tRestore);
|
syncUserQueueMutationToServer(nextItems, nextTrack, tRestore);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import { toQueueItemRefs } from '../utils/library/queueItemRef';
|
|||||||
import { resolveQueueTrack } from '../utils/library/queueTrackView';
|
import { resolveQueueTrack } from '../utils/library/queueTrackView';
|
||||||
import { seedQueueResolver } from '../utils/library/queueTrackResolver';
|
import { seedQueueResolver } from '../utils/library/queueTrackResolver';
|
||||||
import { pushQueueUndoFromGetter } from './queueUndo';
|
import { pushQueueUndoFromGetter } from './queueUndo';
|
||||||
import { syncQueueToServer } from './queueSync';
|
import { syncUserQueueMutationToServer } from './queueSync';
|
||||||
import {
|
import {
|
||||||
clearRadioReconnectTimer,
|
clearRadioReconnectTimer,
|
||||||
playRadioStream,
|
playRadioStream,
|
||||||
@@ -173,7 +173,7 @@ export function createMiscActions(set: SetState, get: GetState): Pick<
|
|||||||
queueIndex: 0,
|
queueIndex: 0,
|
||||||
currentTrack: track,
|
currentTrack: track,
|
||||||
});
|
});
|
||||||
syncQueueToServer(newItems, track, s.currentTime);
|
syncUserQueueMutationToServer(newItems, track, s.currentTime);
|
||||||
if (!wasPlaying) get().resume();
|
if (!wasPlaying) get().resume();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import type { PlayerState, QueueItemRef, Track } from './playerStoreTypes';
|
|||||||
import { toQueueItemRefs } from '../utils/library/queueItemRef';
|
import { toQueueItemRefs } from '../utils/library/queueItemRef';
|
||||||
import { seedQueueResolver } from '../utils/library/queueTrackResolver';
|
import { seedQueueResolver } from '../utils/library/queueTrackResolver';
|
||||||
import { pushQueueUndoFromGetter } from './queueUndo';
|
import { pushQueueUndoFromGetter } from './queueUndo';
|
||||||
import { syncQueueToServer } from './queueSync';
|
import { syncUserQueueMutationToServer } from './queueSync';
|
||||||
import {
|
import {
|
||||||
addRadioSessionSeen,
|
addRadioSessionSeen,
|
||||||
clearRadioSessionSeenIds,
|
clearRadioSessionSeenIds,
|
||||||
@@ -51,7 +51,7 @@ function seedIncoming(state: PlayerState, tracks: Track[]): void {
|
|||||||
/**
|
/**
|
||||||
* Eleven queue-mutation actions. Shared invariant: every action except
|
* Eleven queue-mutation actions. Shared invariant: every action except
|
||||||
* `setRadioArtistId` pushes a queue-undo snapshot and calls
|
* `setRadioArtistId` pushes a queue-undo snapshot and calls
|
||||||
* `syncQueueToServer` so the Navidrome `savePlayQueue` stays in sync.
|
* `syncUserQueueMutationToServer` so the Navidrome `savePlayQueue` stays in sync.
|
||||||
* Exceptions: `enqueue`'s optional third argument **`skipQueueUndo`** and
|
* Exceptions: `enqueue`'s optional third argument **`skipQueueUndo`** and
|
||||||
* **`pruneUpcomingToCurrent(true)`** — Lucky Mix pushes one snapshot up-front.
|
* **`pruneUpcomingToCurrent(true)`** — Lucky Mix pushes one snapshot up-front.
|
||||||
*/
|
*/
|
||||||
@@ -89,7 +89,7 @@ export function createQueueMutationActions(set: SetState, get: GetState): Pick<
|
|||||||
const newItems = firstAutoIdx === -1
|
const newItems = firstAutoIdx === -1
|
||||||
? [...items, ...incoming]
|
? [...items, ...incoming]
|
||||||
: [...items.slice(0, firstAutoIdx), ...incoming, ...items.slice(firstAutoIdx)];
|
: [...items.slice(0, firstAutoIdx), ...incoming, ...items.slice(firstAutoIdx)];
|
||||||
syncQueueToServer(newItems, state.currentTrack, state.currentTime);
|
syncUserQueueMutationToServer(newItems, state.currentTrack, state.currentTime);
|
||||||
prefetchLoudnessForEnqueuedTracks(newItems, state.queueIndex);
|
prefetchLoudnessForEnqueuedTracks(newItems, state.queueIndex);
|
||||||
return { queueItems: newItems };
|
return { queueItems: newItems };
|
||||||
});
|
});
|
||||||
@@ -148,7 +148,7 @@ export function createQueueMutationActions(set: SetState, get: GetState): Pick<
|
|||||||
? [...upcoming, ...incoming]
|
? [...upcoming, ...incoming]
|
||||||
: [...upcoming.slice(0, firstAutoIdx), ...incoming, ...upcoming.slice(firstAutoIdx)];
|
: [...upcoming.slice(0, firstAutoIdx), ...incoming, ...upcoming.slice(firstAutoIdx)];
|
||||||
const newItems = [...beforeAndCurrent, ...mergedItems];
|
const newItems = [...beforeAndCurrent, ...mergedItems];
|
||||||
syncQueueToServer(newItems, state.currentTrack, state.currentTime);
|
syncUserQueueMutationToServer(newItems, state.currentTrack, state.currentTime);
|
||||||
return { queueItems: newItems };
|
return { queueItems: newItems };
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -171,7 +171,7 @@ export function createQueueMutationActions(set: SetState, get: GetState): Pick<
|
|||||||
const newQueueIndex = idx <= state.queueIndex
|
const newQueueIndex = idx <= state.queueIndex
|
||||||
? state.queueIndex + tracks.length
|
? state.queueIndex + tracks.length
|
||||||
: state.queueIndex;
|
: state.queueIndex;
|
||||||
syncQueueToServer(newItems, state.currentTrack, state.currentTime);
|
syncUserQueueMutationToServer(newItems, state.currentTrack, state.currentTime);
|
||||||
prefetchLoudnessForEnqueuedTracks(newItems, newQueueIndex);
|
prefetchLoudnessForEnqueuedTracks(newItems, newQueueIndex);
|
||||||
return { queueItems: newItems, queueIndex: newQueueIndex };
|
return { queueItems: newItems, queueIndex: newQueueIndex };
|
||||||
});
|
});
|
||||||
@@ -202,7 +202,7 @@ export function createQueueMutationActions(set: SetState, get: GetState): Pick<
|
|||||||
if (s.queueItems.length === 0) return;
|
if (s.queueItems.length === 0) return;
|
||||||
if (!skipQueueUndo) pushQueueUndoFromGetter(get);
|
if (!skipQueueUndo) pushQueueUndoFromGetter(get);
|
||||||
set({ queueItems: [], queueIndex: 0 });
|
set({ queueItems: [], queueIndex: 0 });
|
||||||
syncQueueToServer([], null, 0);
|
syncUserQueueMutationToServer([], null, 0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!skipQueueUndo) pushQueueUndoFromGetter(get);
|
if (!skipQueueUndo) pushQueueUndoFromGetter(get);
|
||||||
@@ -216,7 +216,7 @@ export function createQueueMutationActions(set: SetState, get: GetState): Pick<
|
|||||||
: toQueueItemRefs(s.queueServerId ?? '', [s.currentTrack!]);
|
: toQueueItemRefs(s.queueServerId ?? '', [s.currentTrack!]);
|
||||||
const newIndex = at >= 0 ? at : 0;
|
const newIndex = at >= 0 ? at : 0;
|
||||||
set({ queueItems: newItems, queueIndex: newIndex });
|
set({ queueItems: newItems, queueIndex: newIndex });
|
||||||
syncQueueToServer(newItems, s.currentTrack, s.currentTime);
|
syncUserQueueMutationToServer(newItems, s.currentTrack, s.currentTime);
|
||||||
},
|
},
|
||||||
|
|
||||||
clearQueue: () => {
|
clearQueue: () => {
|
||||||
@@ -229,7 +229,7 @@ export function createQueueMutationActions(set: SetState, get: GetState): Pick<
|
|||||||
setCurrentRadioArtistId(null);
|
setCurrentRadioArtistId(null);
|
||||||
clearQueueServerForPlayback();
|
clearQueueServerForPlayback();
|
||||||
set({ queueItems: [], queueIndex: 0, currentTrack: null, isPlaying: false, progress: 0, buffered: 0, currentTime: 0 });
|
set({ queueItems: [], queueIndex: 0, currentTrack: null, isPlaying: false, progress: 0, buffered: 0, currentTime: 0 });
|
||||||
syncQueueToServer([], null, 0);
|
syncUserQueueMutationToServer([], null, 0);
|
||||||
},
|
},
|
||||||
|
|
||||||
reorderQueue: (startIndex, endIndex) => {
|
reorderQueue: (startIndex, endIndex) => {
|
||||||
@@ -242,7 +242,7 @@ export function createQueueMutationActions(set: SetState, get: GetState): Pick<
|
|||||||
let newIndex = queueIndex;
|
let newIndex = queueIndex;
|
||||||
if (currentTrack) newIndex = result.findIndex(r => r.trackId === currentTrack.id);
|
if (currentTrack) newIndex = result.findIndex(r => r.trackId === currentTrack.id);
|
||||||
set({ queueItems: result, queueIndex: Math.max(0, newIndex) });
|
set({ queueItems: result, queueIndex: Math.max(0, newIndex) });
|
||||||
syncQueueToServer(result, currentTrack, get().currentTime);
|
syncUserQueueMutationToServer(result, currentTrack, get().currentTime);
|
||||||
},
|
},
|
||||||
|
|
||||||
shuffleQueue: () => {
|
shuffleQueue: () => {
|
||||||
@@ -262,7 +262,7 @@ export function createQueueMutationActions(set: SetState, get: GetState): Pick<
|
|||||||
: others;
|
: others;
|
||||||
const newIndex = currentIdx >= 0 ? 0 : -1;
|
const newIndex = currentIdx >= 0 ? 0 : -1;
|
||||||
set({ queueItems: result, queueIndex: Math.max(0, newIndex) });
|
set({ queueItems: result, queueIndex: Math.max(0, newIndex) });
|
||||||
syncQueueToServer(result, currentTrack, get().currentTime);
|
syncUserQueueMutationToServer(result, currentTrack, get().currentTime);
|
||||||
},
|
},
|
||||||
|
|
||||||
shuffleUpcomingQueue: () => {
|
shuffleUpcomingQueue: () => {
|
||||||
@@ -281,7 +281,7 @@ export function createQueueMutationActions(set: SetState, get: GetState): Pick<
|
|||||||
}
|
}
|
||||||
const result = [...head, ...upcoming];
|
const result = [...head, ...upcoming];
|
||||||
set({ queueItems: result });
|
set({ queueItems: result });
|
||||||
syncQueueToServer(result, currentTrack, get().currentTime);
|
syncUserQueueMutationToServer(result, currentTrack, get().currentTime);
|
||||||
},
|
},
|
||||||
|
|
||||||
removeTrack: (index) => {
|
removeTrack: (index) => {
|
||||||
@@ -294,7 +294,7 @@ export function createQueueMutationActions(set: SetState, get: GetState): Pick<
|
|||||||
queueItems: newItems,
|
queueItems: newItems,
|
||||||
queueIndex: Math.min(queueIndex, newItems.length - 1),
|
queueIndex: Math.min(queueIndex, newItems.length - 1),
|
||||||
});
|
});
|
||||||
syncQueueToServer(newItems, get().currentTrack, get().currentTime);
|
syncUserQueueMutationToServer(newItems, get().currentTrack, get().currentTime);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ import {
|
|||||||
hasPendingQueueSync,
|
hasPendingQueueSync,
|
||||||
pushQueueOnPlaybackStart,
|
pushQueueOnPlaybackStart,
|
||||||
syncQueueToServer,
|
syncQueueToServer,
|
||||||
|
syncUserQueueMutationToServer,
|
||||||
} from './queueSync';
|
} from './queueSync';
|
||||||
import {
|
import {
|
||||||
_resetQueuePlaybackIdleForTest,
|
_resetQueuePlaybackIdleForTest,
|
||||||
@@ -97,8 +98,17 @@ describe('syncQueueToServer (debounced)', () => {
|
|||||||
expect(savePlayQueueMock).toHaveBeenCalledWith(['a'], 'a', 12000, 'srv-a');
|
expect(savePlayQueueMock).toHaveBeenCalledWith(['a'], 'a', 12000, 'srv-a');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('suspends idle pull on mutation and stays suspended after successful debounced push', async () => {
|
it('does not suspend idle pull during playback sync', () => {
|
||||||
syncQueueToServer(queue, track('a'), 30);
|
syncQueueToServer(queue, track('a'), 30);
|
||||||
|
expect(isIdleQueuePullSuspended()).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('syncUserQueueMutationToServer (debounced)', () => {
|
||||||
|
const queue = [ref('a'), ref('b')];
|
||||||
|
|
||||||
|
it('suspends idle pull on user mutation and stays suspended after successful debounced push', async () => {
|
||||||
|
syncUserQueueMutationToServer(queue, track('a'), 30);
|
||||||
expect(isIdleQueuePullSuspended()).toBe(true);
|
expect(isIdleQueuePullSuspended()).toBe(true);
|
||||||
expect(hasPendingQueueSync()).toBe(true);
|
expect(hasPendingQueueSync()).toBe(true);
|
||||||
vi.advanceTimersByTime(5000);
|
vi.advanceTimersByTime(5000);
|
||||||
@@ -109,7 +119,7 @@ describe('syncQueueToServer (debounced)', () => {
|
|||||||
|
|
||||||
it('keeps idle pull suspended when debounced push fails', async () => {
|
it('keeps idle pull suspended when debounced push fails', async () => {
|
||||||
savePlayQueueMock.mockRejectedValueOnce(new Error('offline'));
|
savePlayQueueMock.mockRejectedValueOnce(new Error('offline'));
|
||||||
syncQueueToServer(queue, track('a'), 30);
|
syncUserQueueMutationToServer(queue, track('a'), 30);
|
||||||
vi.advanceTimersByTime(5000);
|
vi.advanceTimersByTime(5000);
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
expect(isIdleQueuePullSuspended()).toBe(true);
|
expect(isIdleQueuePullSuspended()).toBe(true);
|
||||||
@@ -120,7 +130,7 @@ describe('pushQueueOnPlaybackStart', () => {
|
|||||||
const queue = [ref('a'), ref('b')];
|
const queue = [ref('a'), ref('b')];
|
||||||
|
|
||||||
it('flushes immediately and clears idle pull suspension when locally edited', async () => {
|
it('flushes immediately and clears idle pull suspension when locally edited', async () => {
|
||||||
syncQueueToServer(queue, track('a'), 30);
|
syncUserQueueMutationToServer(queue, track('a'), 30);
|
||||||
expect(hasPendingQueueSync()).toBe(true);
|
expect(hasPendingQueueSync()).toBe(true);
|
||||||
pushQueueOnPlaybackStart(queue, track('a'), 42);
|
pushQueueOnPlaybackStart(queue, track('a'), 42);
|
||||||
expect(hasPendingQueueSync()).toBe(false);
|
expect(hasPendingQueueSync()).toBe(false);
|
||||||
|
|||||||
+24
-4
@@ -18,8 +18,10 @@ import { usePlayerStore } from './playerStore';
|
|||||||
* another client.
|
* another client.
|
||||||
*
|
*
|
||||||
* Two flush shapes:
|
* Two flush shapes:
|
||||||
* - `syncQueueToServer` debounces for 5 s so rapid edits (drag-reorder,
|
* - `syncQueueToServer` debounces playback position/queue pushes (track
|
||||||
* auto-queue trimming, lucky-mix swaps) collapse into a single roundtrip.
|
* changes, resume) without blocking idle auto-pull.
|
||||||
|
* - `syncUserQueueMutationToServer` — same debounce plus idle-pull
|
||||||
|
* suspension for user-initiated queue edits.
|
||||||
* - `flushQueueSyncToServer` cancels the debounce and pushes immediately —
|
* - `flushQueueSyncToServer` cancels the debounce and pushes immediately —
|
||||||
* called from the playback heartbeat, `pause()`, and the app-close path
|
* called from the playback heartbeat, `pause()`, and the app-close path
|
||||||
* where the user might switch devices mid-track.
|
* where the user might switch devices mid-track.
|
||||||
@@ -55,8 +57,11 @@ function pushRefsForServer(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function syncQueueToServer(queue: QueueItemRef[], currentTrack: Track | null, currentTime: number): void {
|
function scheduleQueueSyncToServer(
|
||||||
touchQueueMutationClock();
|
queue: QueueItemRef[],
|
||||||
|
currentTrack: Track | null,
|
||||||
|
currentTime: number,
|
||||||
|
): void {
|
||||||
if (!isPlaybackServerReachable()) return;
|
if (!isPlaybackServerReachable()) return;
|
||||||
if (syncTimeout) clearTimeout(syncTimeout);
|
if (syncTimeout) clearTimeout(syncTimeout);
|
||||||
syncTimeout = setTimeout(() => {
|
syncTimeout = setTimeout(() => {
|
||||||
@@ -68,6 +73,21 @@ export function syncQueueToServer(queue: QueueItemRef[], currentTrack: Track | n
|
|||||||
}, SYNC_DEBOUNCE_MS);
|
}, SYNC_DEBOUNCE_MS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Debounced push during playback (track advance, resume) — does not suspend idle pull. */
|
||||||
|
export function syncQueueToServer(queue: QueueItemRef[], currentTrack: Track | null, currentTime: number): void {
|
||||||
|
scheduleQueueSyncToServer(queue, currentTrack, currentTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Debounced push after a user queue edit — suspends idle auto-pull until manual sync or Play. */
|
||||||
|
export function syncUserQueueMutationToServer(
|
||||||
|
queue: QueueItemRef[],
|
||||||
|
currentTrack: Track | null,
|
||||||
|
currentTime: number,
|
||||||
|
): void {
|
||||||
|
touchQueueMutationClock();
|
||||||
|
scheduleQueueSyncToServer(queue, currentTrack, currentTime);
|
||||||
|
}
|
||||||
|
|
||||||
export function flushQueueSyncToServer(queue: QueueItemRef[], currentTrack: Track | null, currentTime: number): Promise<void> {
|
export function flushQueueSyncToServer(queue: QueueItemRef[], currentTrack: Track | null, currentTime: number): Promise<void> {
|
||||||
if (syncTimeout) {
|
if (syncTimeout) {
|
||||||
clearTimeout(syncTimeout);
|
clearTimeout(syncTimeout);
|
||||||
|
|||||||
Reference in New Issue
Block a user