mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-22 14:35:41 +00:00
refactor(player): E.30 — extract applyQueueHistorySnapshot (#594)
The ~150-LOC `applyQueueHistorySnapshot` helper that lived inside the
playerStore `create((set, get) => { … })` closure moves out into
`src/store/applyQueueHistorySnapshot.ts`. The function now takes the
zustand `set` / `get` references as explicit parameters; the two
caller sites (`undoLastQueueEdit`, `redoLastQueueEdit`) pass them
through unchanged.
Side-cleanup: four imports that only fed the moved helper drop from
playerStore (getPlaybackSourceKind, queueUndoRestoreAudioEngine,
setPendingQueueListScrollTop, shallowCloneQueueTracks).
playerStore 1706 → 1550 LOC (−156).
The undo/redo flow keeps behaving 1:1 — the function is exported and
deterministic; the closure capture only existed for the set/get pair
which is trivial to pass through.
This commit is contained in:
committed by
GitHub
parent
6438fff019
commit
8dfe05fe7d
@@ -0,0 +1,205 @@
|
|||||||
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
|
import { reportNowPlaying } from '../api/subsonic';
|
||||||
|
import { getPlaybackSourceKind } from '../utils/resolvePlaybackUrl';
|
||||||
|
import { useAuthStore } from './authStore';
|
||||||
|
import {
|
||||||
|
bumpPlayGeneration,
|
||||||
|
getPlayGeneration,
|
||||||
|
setIsAudioPaused,
|
||||||
|
} from './engineState';
|
||||||
|
import { clearPreloadingIds } from './gaplessPreloadState';
|
||||||
|
import { deriveNormalizationSnapshot } from './normalizationSnapshot';
|
||||||
|
import type { PlayerState } from './playerStoreTypes';
|
||||||
|
import { sameQueueTrackId, shallowCloneQueueTracks } from '../utils/queueIdentity';
|
||||||
|
import { queueUndoRestoreAudioEngine } from './queueUndoAudioRestore';
|
||||||
|
import {
|
||||||
|
setPendingQueueListScrollTop,
|
||||||
|
type QueueUndoSnapshot,
|
||||||
|
} from './queueUndo';
|
||||||
|
import { refreshLoudnessForTrack } from './loudnessRefresh';
|
||||||
|
import { refreshWaveformForTrack } from './waveformRefresh';
|
||||||
|
import { stopRadio } from './radioPlayer';
|
||||||
|
import { clearAllPlaybackScheduleTimers } from './scheduleTimers';
|
||||||
|
import { syncQueueToServer } from './queueSync';
|
||||||
|
|
||||||
|
type SetState = (
|
||||||
|
partial: Partial<PlayerState> | ((state: PlayerState) => Partial<PlayerState>),
|
||||||
|
) => void;
|
||||||
|
type GetState = () => PlayerState;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply a queue-undo snapshot to the player store and resync the Rust
|
||||||
|
* audio engine where needed. Used by both `undoLastQueueEdit` and
|
||||||
|
* `redoLastQueueEdit` actions inside the store.
|
||||||
|
*
|
||||||
|
* Behaviour matrix:
|
||||||
|
* - **Snapshot has no current track but playback is live** — keep the
|
||||||
|
* playing track and prepend it to the restored queue (or rebind by
|
||||||
|
* id when it's already there).
|
||||||
|
* - **Snapshot's current track matches the live one** — keep playback
|
||||||
|
* going, restore queue + position only.
|
||||||
|
* - **Snapshot's current track differs** — issue a full audio_play via
|
||||||
|
* `queueUndoRestoreAudioEngine` to put the engine on the snapshot
|
||||||
|
* track at the captured position.
|
||||||
|
*
|
||||||
|
* Returns false only when nothing changed (caller shows no toast).
|
||||||
|
*/
|
||||||
|
export function applyQueueHistorySnapshot(
|
||||||
|
snap: QueueUndoSnapshot,
|
||||||
|
prior: PlayerState,
|
||||||
|
set: SetState,
|
||||||
|
get: GetState,
|
||||||
|
): boolean {
|
||||||
|
if (prior.currentRadio) {
|
||||||
|
stopRadio();
|
||||||
|
}
|
||||||
|
let nextQueue = shallowCloneQueueTracks(snap.queue);
|
||||||
|
let nextIndex = snap.queueIndex;
|
||||||
|
let nextTrack = snap.currentTrack ? { ...snap.currentTrack } : null;
|
||||||
|
|
||||||
|
if (snap.currentTrack == null && prior.currentTrack) {
|
||||||
|
const playing = prior.currentTrack;
|
||||||
|
const pos = nextQueue.findIndex(t => sameQueueTrackId(t.id, playing.id));
|
||||||
|
if (pos === -1) {
|
||||||
|
nextQueue = [{ ...playing }, ...nextQueue];
|
||||||
|
nextIndex = 0;
|
||||||
|
nextTrack = { ...playing };
|
||||||
|
} else {
|
||||||
|
nextTrack = { ...playing };
|
||||||
|
nextIndex = pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nextIndex = Math.max(0, Math.min(nextIndex, Math.max(0, nextQueue.length - 1)));
|
||||||
|
|
||||||
|
const keepPlaybackFromPrior =
|
||||||
|
prior.currentTrack != null
|
||||||
|
&& nextTrack != null
|
||||||
|
&& sameQueueTrackId(prior.currentTrack.id, nextTrack.id)
|
||||||
|
&& nextQueue.some(t => sameQueueTrackId(t.id, prior.currentTrack!.id))
|
||||||
|
&& (
|
||||||
|
(snap.currentTrack != null && sameQueueTrackId(prior.currentTrack.id, snap.currentTrack.id))
|
||||||
|
|| snap.currentTrack == null
|
||||||
|
);
|
||||||
|
|
||||||
|
if (keepPlaybackFromPrior) {
|
||||||
|
const playingKeep = prior.currentTrack;
|
||||||
|
if (playingKeep) {
|
||||||
|
const idxPrior = nextQueue.findIndex(t => sameQueueTrackId(t.id, playingKeep.id));
|
||||||
|
if (idxPrior >= 0) {
|
||||||
|
nextIndex = idxPrior;
|
||||||
|
nextTrack = { ...playingKeep };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let tRestoreRaw = typeof snap.currentTime === 'number' && Number.isFinite(snap.currentTime)
|
||||||
|
? snap.currentTime
|
||||||
|
: 0;
|
||||||
|
let playingRestore = snap.isPlaying !== false;
|
||||||
|
if (keepPlaybackFromPrior && prior.currentTrack) {
|
||||||
|
tRestoreRaw = prior.currentTime;
|
||||||
|
playingRestore = prior.isPlaying;
|
||||||
|
}
|
||||||
|
const durForProgress = nextTrack?.duration && nextTrack.duration > 0 ? nextTrack.duration : null;
|
||||||
|
let pRestore = typeof snap.progress === 'number' && Number.isFinite(snap.progress)
|
||||||
|
? snap.progress
|
||||||
|
: (durForProgress != null && durForProgress > 0
|
||||||
|
? Math.max(0, Math.min(1, tRestoreRaw / durForProgress))
|
||||||
|
: 0);
|
||||||
|
if (keepPlaybackFromPrior) {
|
||||||
|
pRestore = prior.progress;
|
||||||
|
}
|
||||||
|
const tRestore = durForProgress != null
|
||||||
|
? Math.max(0, Math.min(tRestoreRaw, durForProgress))
|
||||||
|
: Math.max(0, tRestoreRaw);
|
||||||
|
|
||||||
|
const keepWaveform =
|
||||||
|
prior.currentTrack?.id != null &&
|
||||||
|
nextTrack?.id != null &&
|
||||||
|
sameQueueTrackId(prior.currentTrack.id, nextTrack.id);
|
||||||
|
const norm =
|
||||||
|
nextTrack != null
|
||||||
|
? deriveNormalizationSnapshot(nextTrack, nextQueue, nextIndex)
|
||||||
|
: ({
|
||||||
|
normalizationNowDb: null,
|
||||||
|
normalizationTargetLufs: null,
|
||||||
|
normalizationEngineLive: 'off',
|
||||||
|
} as Pick<
|
||||||
|
PlayerState,
|
||||||
|
'normalizationNowDb' | 'normalizationTargetLufs' | 'normalizationEngineLive'
|
||||||
|
>);
|
||||||
|
const authSnap = useAuthStore.getState();
|
||||||
|
const playbackSourceUndo = nextTrack
|
||||||
|
? getPlaybackSourceKind(nextTrack.id, authSnap.activeServerId ?? '', null)
|
||||||
|
: null;
|
||||||
|
const playbackSourceFinal = keepPlaybackFromPrior && prior.currentPlaybackSource != null
|
||||||
|
? prior.currentPlaybackSource
|
||||||
|
: playbackSourceUndo;
|
||||||
|
|
||||||
|
clearAllPlaybackScheduleTimers();
|
||||||
|
set({
|
||||||
|
scheduledPauseAtMs: null,
|
||||||
|
scheduledPauseStartMs: null,
|
||||||
|
scheduledResumeAtMs: null,
|
||||||
|
scheduledResumeStartMs: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
clearPreloadingIds();
|
||||||
|
|
||||||
|
let gen = getPlayGeneration();
|
||||||
|
const resyncEngine = Boolean(nextTrack) && !keepPlaybackFromPrior;
|
||||||
|
if (resyncEngine || !nextTrack) {
|
||||||
|
gen = bumpPlayGeneration();
|
||||||
|
if (resyncEngine) {
|
||||||
|
setIsAudioPaused(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set({
|
||||||
|
queue: nextQueue,
|
||||||
|
queueIndex: nextIndex,
|
||||||
|
currentTrack: nextTrack,
|
||||||
|
currentRadio: null,
|
||||||
|
currentTime: tRestore,
|
||||||
|
progress: pRestore,
|
||||||
|
isPlaying: playingRestore,
|
||||||
|
waveformBins: keepWaveform ? prior.waveformBins : null,
|
||||||
|
enginePreloadedTrackId: keepPlaybackFromPrior ? prior.enginePreloadedTrackId : null,
|
||||||
|
currentPlaybackSource: playbackSourceFinal,
|
||||||
|
...norm,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!nextTrack) {
|
||||||
|
invoke('audio_stop').catch(console.error);
|
||||||
|
setIsAudioPaused(false);
|
||||||
|
syncQueueToServer(nextQueue, null, 0);
|
||||||
|
if (typeof snap.queueListScrollTop === 'number' && Number.isFinite(snap.queueListScrollTop)) {
|
||||||
|
setPendingQueueListScrollTop(Math.max(0, snap.queueListScrollTop));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void refreshWaveformForTrack(nextTrack.id);
|
||||||
|
void refreshLoudnessForTrack(nextTrack.id);
|
||||||
|
get().updateReplayGainForCurrentTrack();
|
||||||
|
|
||||||
|
if (!keepPlaybackFromPrior) {
|
||||||
|
const { nowPlayingEnabled: npUndo } = useAuthStore.getState();
|
||||||
|
if (npUndo) reportNowPlaying(nextTrack.id);
|
||||||
|
|
||||||
|
queueUndoRestoreAudioEngine({
|
||||||
|
generation: gen,
|
||||||
|
track: nextTrack,
|
||||||
|
queue: nextQueue,
|
||||||
|
queueIndex: nextIndex,
|
||||||
|
atSeconds: tRestore,
|
||||||
|
wantPlaying: playingRestore,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (typeof snap.queueListScrollTop === 'number' && Number.isFinite(snap.queueListScrollTop)) {
|
||||||
|
setPendingQueueListScrollTop(Math.max(0, snap.queueListScrollTop));
|
||||||
|
}
|
||||||
|
syncQueueToServer(nextQueue, nextTrack, tRestore);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
+4
-160
@@ -4,7 +4,7 @@ import { invoke } from '@tauri-apps/api/core';
|
|||||||
import { showToast } from '../utils/toast';
|
import { showToast } from '../utils/toast';
|
||||||
import i18n from '../i18n';
|
import i18n from '../i18n';
|
||||||
import { buildStreamUrl, getPlayQueue, savePlayQueue, reportNowPlaying, getSong, getSimilarSongs2, getTopSongs, setRating } from '../api/subsonic';
|
import { buildStreamUrl, getPlayQueue, savePlayQueue, reportNowPlaying, getSong, getSimilarSongs2, getTopSongs, setRating } from '../api/subsonic';
|
||||||
import { resolvePlaybackUrl, getPlaybackSourceKind } from '../utils/resolvePlaybackUrl';
|
import { resolvePlaybackUrl } from '../utils/resolvePlaybackUrl';
|
||||||
import { setDeferHotCachePrefetch } from '../utils/hotCacheGate';
|
import { setDeferHotCachePrefetch } from '../utils/hotCacheGate';
|
||||||
import { lastfmUpdateNowPlaying, lastfmLoveTrack, lastfmUnloveTrack, lastfmGetTrackLoved, lastfmGetAllLovedTracks } from '../api/lastfm';
|
import { lastfmUpdateNowPlaying, lastfmLoveTrack, lastfmUnloveTrack, lastfmGetTrackLoved, lastfmGetAllLovedTracks } from '../api/lastfm';
|
||||||
import { useAuthStore } from './authStore';
|
import { useAuthStore } from './authStore';
|
||||||
@@ -22,7 +22,6 @@ import { buildInfiniteQueueCandidates } from '../utils/buildInfiniteQueueCandida
|
|||||||
import {
|
import {
|
||||||
queuesStructuralEqual,
|
queuesStructuralEqual,
|
||||||
sameQueueTrackId,
|
sameQueueTrackId,
|
||||||
shallowCloneQueueTracks,
|
|
||||||
} from '../utils/queueIdentity';
|
} from '../utils/queueIdentity';
|
||||||
import { waveformBlobLenOk } from '../utils/waveformParse';
|
import { waveformBlobLenOk } from '../utils/waveformParse';
|
||||||
import { isRecoverableSeekError } from '../utils/seekErrors';
|
import { isRecoverableSeekError } from '../utils/seekErrors';
|
||||||
@@ -117,7 +116,6 @@ import {
|
|||||||
isInfiniteQueueFetching,
|
isInfiniteQueueFetching,
|
||||||
setInfiniteQueueFetching,
|
setInfiniteQueueFetching,
|
||||||
} from './infiniteQueueState';
|
} from './infiniteQueueState';
|
||||||
import { queueUndoRestoreAudioEngine } from './queueUndoAudioRestore';
|
|
||||||
import { prefetchLoudnessForEnqueuedTracks } from './loudnessPrefetch';
|
import { prefetchLoudnessForEnqueuedTracks } from './loudnessPrefetch';
|
||||||
import { initAudioListeners } from './initAudioListeners';
|
import { initAudioListeners } from './initAudioListeners';
|
||||||
import { installQueueUndoHotkey } from './queueUndoHotkey';
|
import { installQueueUndoHotkey } from './queueUndoHotkey';
|
||||||
@@ -157,7 +155,6 @@ import {
|
|||||||
pushQueueUndoSnapshot,
|
pushQueueUndoSnapshot,
|
||||||
queueUndoSnapshotFromState,
|
queueUndoSnapshotFromState,
|
||||||
registerQueueListScrollTopReader,
|
registerQueueListScrollTopReader,
|
||||||
setPendingQueueListScrollTop,
|
|
||||||
type QueueUndoSnapshot,
|
type QueueUndoSnapshot,
|
||||||
} from './queueUndo';
|
} from './queueUndo';
|
||||||
|
|
||||||
@@ -176,6 +173,7 @@ export {
|
|||||||
|
|
||||||
import type { PlayerState, Track } from './playerStoreTypes';
|
import type { PlayerState, Track } from './playerStoreTypes';
|
||||||
export type { PlayerState, Track };
|
export type { PlayerState, Track };
|
||||||
|
import { applyQueueHistorySnapshot } from './applyQueueHistorySnapshot';
|
||||||
|
|
||||||
|
|
||||||
// ─── Module-level playback primitives ─────────────────────────────────────────
|
// ─── Module-level playback primitives ─────────────────────────────────────────
|
||||||
@@ -186,160 +184,6 @@ export type { PlayerState, Track };
|
|||||||
export const usePlayerStore = create<PlayerState>()(
|
export const usePlayerStore = create<PlayerState>()(
|
||||||
persist(
|
persist(
|
||||||
(set, get) => {
|
(set, get) => {
|
||||||
function applyQueueHistorySnapshot(snap: QueueUndoSnapshot, prior: PlayerState): boolean {
|
|
||||||
if (prior.currentRadio) {
|
|
||||||
stopRadio();
|
|
||||||
}
|
|
||||||
let nextQueue = shallowCloneQueueTracks(snap.queue);
|
|
||||||
let nextIndex = snap.queueIndex;
|
|
||||||
let nextTrack = snap.currentTrack ? { ...snap.currentTrack } : null;
|
|
||||||
|
|
||||||
if (snap.currentTrack == null && prior.currentTrack) {
|
|
||||||
const playing = prior.currentTrack;
|
|
||||||
const pos = nextQueue.findIndex(t => sameQueueTrackId(t.id, playing.id));
|
|
||||||
if (pos === -1) {
|
|
||||||
nextQueue = [{ ...playing }, ...nextQueue];
|
|
||||||
nextIndex = 0;
|
|
||||||
nextTrack = { ...playing };
|
|
||||||
} else {
|
|
||||||
nextTrack = { ...playing };
|
|
||||||
nextIndex = pos;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
nextIndex = Math.max(0, Math.min(nextIndex, Math.max(0, nextQueue.length - 1)));
|
|
||||||
|
|
||||||
const keepPlaybackFromPrior =
|
|
||||||
prior.currentTrack != null
|
|
||||||
&& nextTrack != null
|
|
||||||
&& sameQueueTrackId(prior.currentTrack.id, nextTrack.id)
|
|
||||||
&& nextQueue.some(t => sameQueueTrackId(t.id, prior.currentTrack!.id))
|
|
||||||
&& (
|
|
||||||
(snap.currentTrack != null && sameQueueTrackId(prior.currentTrack.id, snap.currentTrack.id))
|
|
||||||
|| snap.currentTrack == null
|
|
||||||
);
|
|
||||||
|
|
||||||
if (keepPlaybackFromPrior) {
|
|
||||||
const playingKeep = prior.currentTrack;
|
|
||||||
if (playingKeep) {
|
|
||||||
const idxPrior = nextQueue.findIndex(t => sameQueueTrackId(t.id, playingKeep.id));
|
|
||||||
if (idxPrior >= 0) {
|
|
||||||
nextIndex = idxPrior;
|
|
||||||
nextTrack = { ...playingKeep };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let tRestoreRaw = typeof snap.currentTime === 'number' && Number.isFinite(snap.currentTime)
|
|
||||||
? snap.currentTime
|
|
||||||
: 0;
|
|
||||||
let playingRestore = snap.isPlaying !== false;
|
|
||||||
if (keepPlaybackFromPrior && prior.currentTrack) {
|
|
||||||
tRestoreRaw = prior.currentTime;
|
|
||||||
playingRestore = prior.isPlaying;
|
|
||||||
}
|
|
||||||
const durForProgress = nextTrack?.duration && nextTrack.duration > 0 ? nextTrack.duration : null;
|
|
||||||
let pRestore = typeof snap.progress === 'number' && Number.isFinite(snap.progress)
|
|
||||||
? snap.progress
|
|
||||||
: (durForProgress != null && durForProgress > 0
|
|
||||||
? Math.max(0, Math.min(1, tRestoreRaw / durForProgress))
|
|
||||||
: 0);
|
|
||||||
if (keepPlaybackFromPrior) {
|
|
||||||
pRestore = prior.progress;
|
|
||||||
}
|
|
||||||
const tRestore = durForProgress != null
|
|
||||||
? Math.max(0, Math.min(tRestoreRaw, durForProgress))
|
|
||||||
: Math.max(0, tRestoreRaw);
|
|
||||||
|
|
||||||
const keepWaveform =
|
|
||||||
prior.currentTrack?.id != null &&
|
|
||||||
nextTrack?.id != null &&
|
|
||||||
sameQueueTrackId(prior.currentTrack.id, nextTrack.id);
|
|
||||||
const norm =
|
|
||||||
nextTrack != null
|
|
||||||
? deriveNormalizationSnapshot(nextTrack, nextQueue, nextIndex)
|
|
||||||
: ({
|
|
||||||
normalizationNowDb: null,
|
|
||||||
normalizationTargetLufs: null,
|
|
||||||
normalizationEngineLive: 'off',
|
|
||||||
} as Pick<
|
|
||||||
PlayerState,
|
|
||||||
'normalizationNowDb' | 'normalizationTargetLufs' | 'normalizationEngineLive'
|
|
||||||
>);
|
|
||||||
const authSnap = useAuthStore.getState();
|
|
||||||
const playbackSourceUndo = nextTrack
|
|
||||||
? getPlaybackSourceKind(nextTrack.id, authSnap.activeServerId ?? '', null)
|
|
||||||
: null;
|
|
||||||
const playbackSourceFinal = keepPlaybackFromPrior && prior.currentPlaybackSource != null
|
|
||||||
? prior.currentPlaybackSource
|
|
||||||
: playbackSourceUndo;
|
|
||||||
|
|
||||||
clearAllPlaybackScheduleTimers();
|
|
||||||
set({
|
|
||||||
scheduledPauseAtMs: null,
|
|
||||||
scheduledPauseStartMs: null,
|
|
||||||
scheduledResumeAtMs: null,
|
|
||||||
scheduledResumeStartMs: null,
|
|
||||||
});
|
|
||||||
|
|
||||||
clearPreloadingIds();
|
|
||||||
|
|
||||||
let gen = getPlayGeneration();
|
|
||||||
const resyncEngine = Boolean(nextTrack) && !keepPlaybackFromPrior;
|
|
||||||
if (resyncEngine || !nextTrack) {
|
|
||||||
gen = bumpPlayGeneration();
|
|
||||||
if (resyncEngine) {
|
|
||||||
setIsAudioPaused(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
set({
|
|
||||||
queue: nextQueue,
|
|
||||||
queueIndex: nextIndex,
|
|
||||||
currentTrack: nextTrack,
|
|
||||||
currentRadio: null,
|
|
||||||
currentTime: tRestore,
|
|
||||||
progress: pRestore,
|
|
||||||
isPlaying: playingRestore,
|
|
||||||
waveformBins: keepWaveform ? prior.waveformBins : null,
|
|
||||||
enginePreloadedTrackId: keepPlaybackFromPrior ? prior.enginePreloadedTrackId : null,
|
|
||||||
currentPlaybackSource: playbackSourceFinal,
|
|
||||||
...norm,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!nextTrack) {
|
|
||||||
invoke('audio_stop').catch(console.error);
|
|
||||||
setIsAudioPaused(false);
|
|
||||||
syncQueueToServer(nextQueue, null, 0);
|
|
||||||
if (typeof snap.queueListScrollTop === 'number' && Number.isFinite(snap.queueListScrollTop)) {
|
|
||||||
setPendingQueueListScrollTop(Math.max(0, snap.queueListScrollTop));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void refreshWaveformForTrack(nextTrack.id);
|
|
||||||
void refreshLoudnessForTrack(nextTrack.id);
|
|
||||||
get().updateReplayGainForCurrentTrack();
|
|
||||||
|
|
||||||
if (!keepPlaybackFromPrior) {
|
|
||||||
const { nowPlayingEnabled: npUndo } = useAuthStore.getState();
|
|
||||||
if (npUndo) reportNowPlaying(nextTrack.id);
|
|
||||||
|
|
||||||
queueUndoRestoreAudioEngine({
|
|
||||||
generation: gen,
|
|
||||||
track: nextTrack,
|
|
||||||
queue: nextQueue,
|
|
||||||
queueIndex: nextIndex,
|
|
||||||
atSeconds: tRestore,
|
|
||||||
wantPlaying: playingRestore,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (typeof snap.queueListScrollTop === 'number' && Number.isFinite(snap.queueListScrollTop)) {
|
|
||||||
setPendingQueueListScrollTop(Math.max(0, snap.queueListScrollTop));
|
|
||||||
}
|
|
||||||
syncQueueToServer(nextQueue, nextTrack, tRestore);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
currentTrack: null,
|
currentTrack: null,
|
||||||
@@ -1555,7 +1399,7 @@ export const usePlayerStore = create<PlayerState>()(
|
|||||||
const snap = popQueueUndoSnapshot();
|
const snap = popQueueUndoSnapshot();
|
||||||
if (!snap) return false;
|
if (!snap) return false;
|
||||||
pushQueueRedoSnapshot(queueUndoSnapshotFromState(prior));
|
pushQueueRedoSnapshot(queueUndoSnapshotFromState(prior));
|
||||||
return applyQueueHistorySnapshot(snap, prior);
|
return applyQueueHistorySnapshot(snap, prior, set, get);
|
||||||
},
|
},
|
||||||
|
|
||||||
redoLastQueueEdit: () => {
|
redoLastQueueEdit: () => {
|
||||||
@@ -1563,7 +1407,7 @@ export const usePlayerStore = create<PlayerState>()(
|
|||||||
const snap = popQueueRedoSnapshot();
|
const snap = popQueueRedoSnapshot();
|
||||||
if (!snap) return false;
|
if (!snap) return false;
|
||||||
pushQueueUndoSnapshot(queueUndoSnapshotFromState(prior));
|
pushQueueUndoSnapshot(queueUndoSnapshotFromState(prior));
|
||||||
return applyQueueHistorySnapshot(snap, prior);
|
return applyQueueHistorySnapshot(snap, prior, set, get);
|
||||||
},
|
},
|
||||||
|
|
||||||
removeTrack: (index) => {
|
removeTrack: (index) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user