mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 15:25:46 +00:00
refactor(playback): move the audio engine into features/playback
Relocate the playback/queue/transport/audio-output engine out of the type-first
store/ + utils/playback/ + utils/audio/ dirs into a cohesive src/features/playback/,
structure-preserving:
store/<x> -> features/playback/store/<x>
store/audioListenerSetup/<x> -> features/playback/store/audioListenerSetup/<x>
utils/playback/<x> -> features/playback/utils/playback/<x>
utils/audio/<x> -> features/playback/utils/audio/<x>
184 files moved (107 source + 77 tests), 365 consumers rewritten. Pure move — no
behavior change, no state-split (the playerStore state-split stays a separate M5
question). Enabled by this session's decouple seams (artist/offline/orbit/auth →
core registries), so the engine carries no inbound core->feature inversion: store/
now holds only the 50 cross-cutting global stores (auth family, the seams, library
index, UI/settings stores).
KEPT OUT of the move (would re-create global->engine edges): the 3 pure config
helpers utils/audio/{loudnessPreAnalysisSlider,hiResCrossfadeResample} +
utils/playback/autodjOverlapCap (authStore + settings UI read them — they stay in
utils/). Ambiguous view-state stores (eqStore, queueToolbarStore,
playerBarLayoutStore) stay global (no engine imports).
Consumers use DEEP paths (@/features/playback/...), no barrel — matches the lib/
approach and avoids barrel-mock-collapse across the 140 usePlayerStore consumers.
Two tolerated type-only core->feature edges remain (localPlaybackStore->QueueItemRef,
localPlaybackMigration->HotCacheEntry, both erased).
tsc 0, lint 0, full suite 319/2353 green, iron-rule clean (no runtime store->feature
import). Behavior-touching only via the prerequisite bridge seam (already QA-flagged);
the move itself is pure.
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
import type { QueueItemRef } from '@/features/playback/store/playerStoreTypes';
|
||||
import { create } from 'zustand';
|
||||
import type { HotCacheEntry } from '@/features/playback/store/hotCacheStoreTypes';
|
||||
import { useLocalPlaybackStore, type LocalPlaybackEntry } from '@/store/localPlaybackStore';
|
||||
import { entryBelongsToServer } from '@/store/localPlaybackResolve';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { getMediaDir } from '@/utils/media/mediaDir';
|
||||
|
||||
export type { HotCacheEntry } from '@/features/playback/store/hotCacheStoreTypes';
|
||||
/** @deprecated Use {@link LOCAL_PLAYBACK_PROTECT_AFTER_CURRENT}. */
|
||||
export const HOT_CACHE_PROTECT_AFTER_CURRENT = 1;
|
||||
|
||||
interface HotCacheState {
|
||||
getLocalUrl: (trackId: string, serverId: string) => string | null;
|
||||
setEntry: (
|
||||
trackId: string,
|
||||
serverId: string,
|
||||
localPath: string,
|
||||
sizeBytes: number,
|
||||
debugSource?: string,
|
||||
layoutFingerprint?: string,
|
||||
suffix?: string,
|
||||
) => void;
|
||||
touchPlayed: (trackId: string, serverId: string) => void;
|
||||
removeEntry: (trackId: string, serverId: string) => Promise<void>;
|
||||
totalBytes: () => number;
|
||||
evictToFit: (
|
||||
queue: QueueItemRef[],
|
||||
queueIndex: number,
|
||||
maxBytes: number,
|
||||
activeServerId: string,
|
||||
mediaDir: string | null,
|
||||
) => Promise<void>;
|
||||
clearAllDisk: (mediaDir: string | null) => Promise<void>;
|
||||
}
|
||||
|
||||
/** Ephemeral-tier view for UI selectors (Settings track count, prefetch helpers). */
|
||||
export function selectHotCacheEntries(
|
||||
entries: Record<string, import('@/store/localPlaybackStore').LocalPlaybackEntry>,
|
||||
): Record<string, HotCacheEntry> {
|
||||
const out: Record<string, HotCacheEntry> = {};
|
||||
for (const [key, e] of Object.entries(entries)) {
|
||||
if (e.tier !== 'ephemeral') continue;
|
||||
out[key] = {
|
||||
localPath: e.localPath,
|
||||
sizeBytes: e.sizeBytes,
|
||||
cachedAt: e.cachedAt,
|
||||
lastPlayedAt: e.lastPlayedAt,
|
||||
};
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** Ephemeral-tier row count for Settings (optional active-server scope). */
|
||||
export function countHotCacheTracks(
|
||||
entries: Record<string, LocalPlaybackEntry>,
|
||||
scopeServerId?: string,
|
||||
): number {
|
||||
let n = 0;
|
||||
for (const e of Object.values(entries)) {
|
||||
if (e.tier !== 'ephemeral') continue;
|
||||
if (scopeServerId && !entryBelongsToServer(e, scopeServerId)) continue;
|
||||
n++;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
export const useHotCacheStore = create<HotCacheState>()(() => ({
|
||||
getLocalUrl: (trackId, serverId) =>
|
||||
useLocalPlaybackStore.getState().getLocalUrl(trackId, serverId, 'ephemeral'),
|
||||
|
||||
setEntry: (trackId, serverId, localPath, sizeBytes, _debugSource, layoutFingerprint = '', suffix = 'mp3') => {
|
||||
useLocalPlaybackStore.getState().upsertEntry({
|
||||
serverIndexKey: serverId,
|
||||
trackId,
|
||||
localPath,
|
||||
sizeBytes,
|
||||
layoutFingerprint,
|
||||
tier: 'ephemeral',
|
||||
suffix,
|
||||
});
|
||||
},
|
||||
|
||||
touchPlayed: (trackId, serverId) => {
|
||||
useLocalPlaybackStore.getState().touchPlayed(trackId, serverId);
|
||||
},
|
||||
|
||||
removeEntry: async (trackId, serverId) => {
|
||||
const lp = useLocalPlaybackStore.getState();
|
||||
const e = lp.getEntry(trackId, serverId);
|
||||
if (e?.tier === 'ephemeral' && e.localPath) {
|
||||
await invoke('delete_media_file', { localPath: e.localPath, mediaDir: getMediaDir() }).catch(
|
||||
() => {},
|
||||
);
|
||||
lp.removeEntry(trackId, serverId, 'hot-cache-shim');
|
||||
}
|
||||
},
|
||||
|
||||
totalBytes: () => useLocalPlaybackStore.getState().ephemeralTotalBytes(),
|
||||
|
||||
evictToFit: async (queue, queueIndex, maxBytes, activeServerId, mediaDir) => {
|
||||
await useLocalPlaybackStore.getState().evictEphemeralToFit(
|
||||
queue,
|
||||
queueIndex,
|
||||
maxBytes,
|
||||
activeServerId,
|
||||
mediaDir,
|
||||
);
|
||||
},
|
||||
|
||||
clearAllDisk: async (mediaDir) => {
|
||||
await useLocalPlaybackStore.getState().purgeEphemeralDisk(mediaDir);
|
||||
},
|
||||
}));
|
||||
Reference in New Issue
Block a user