mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 15:25:46 +00:00
refactor(decouple): playback-engine bridge — break authStore→engine edges
The last core→engine inversions blocking the playback-core move live in the authStore settings/profile family: authServerProfileActions reads usePlayerStore.queueServerId + calls clearQueueServerForPlayback (on server delete), and authAudioSettingsActions calls usePlayerStore.updateReplayGainForCurrentTrack from ~8 ReplayGain/normalization setters. Both are re-exported through the authStore barrel, so a global store would depend on the (soon-to-move) engine. Add core seam store/playbackEngineBridge.ts: a registry exposing getQueueServerId / clearQueueServerForPlayback / updateReplayGainForCurrentTrack with no-op/null defaults. The engine registers its impls via store/playbackEngineBridgeRegister.ts (side-effect-imported by MainApp at boot). authStore actions now call the neutral delegators instead of importing the engine. Default is safe: the only callers are user-triggered settings/profile actions that fire long after boot (engine already registered); at boot there's no current track or queue binding, so the no-op would be correct anyway. authStore.servers.test imports the register module so removeServer's queue-clear runs through the real engine wiring. Prepares the playback-core move (Engine → features/playback): with this + keeping the 3 config-helper utils in utils/ + tolerating 2 type-only edges, the engine has no inbound core→feature inversions left. tsc 0, lint 0, suite 319/2353 green. Behavior-touching (server-delete queue clear, settings→gain refresh) → Frank QA.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { initAudioListeners } from '../store/initAudioListeners';
|
||||
import '../store/playbackEngineBridgeRegister'; // installs the playback-engine bridge at boot
|
||||
import { lazy, Suspense, useEffect, useState } from 'react';
|
||||
import { BrowserRouter, Route, Routes } from 'react-router-dom';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
sanitizeAutodjOverlapCapSec,
|
||||
} from '../utils/playback/autodjOverlapCap';
|
||||
import { DEFAULT_LOUDNESS_PRE_ANALYSIS_ATTENUATION_DB } from './authStoreDefaults';
|
||||
import { usePlayerStore } from './playerStore';
|
||||
import { updateReplayGainForCurrentTrack } from './playbackEngineBridge';
|
||||
import type { AuthState } from './authStoreTypes';
|
||||
|
||||
type SetState = (
|
||||
@@ -43,15 +43,15 @@ export function createAudioSettingsActions(set: SetState): Pick<
|
||||
return {
|
||||
setReplayGainEnabled: (v) => {
|
||||
set({ replayGainEnabled: v });
|
||||
usePlayerStore.getState().updateReplayGainForCurrentTrack();
|
||||
updateReplayGainForCurrentTrack();
|
||||
},
|
||||
setNormalizationEngine: (v) => {
|
||||
set({ normalizationEngine: v });
|
||||
usePlayerStore.getState().updateReplayGainForCurrentTrack();
|
||||
updateReplayGainForCurrentTrack();
|
||||
},
|
||||
setLoudnessTargetLufs: (v) => {
|
||||
set({ loudnessTargetLufs: v });
|
||||
usePlayerStore.getState().updateReplayGainForCurrentTrack();
|
||||
updateReplayGainForCurrentTrack();
|
||||
},
|
||||
setLoudnessPreAnalysisAttenuationDb: (v) => {
|
||||
const n = typeof v === 'number' ? v : Number(v);
|
||||
@@ -60,19 +60,19 @@ export function createAudioSettingsActions(set: SetState): Pick<
|
||||
},
|
||||
resetLoudnessPreAnalysisAttenuationDbDefault: () => {
|
||||
set({ loudnessPreAnalysisAttenuationDb: DEFAULT_LOUDNESS_PRE_ANALYSIS_ATTENUATION_DB });
|
||||
usePlayerStore.getState().updateReplayGainForCurrentTrack();
|
||||
updateReplayGainForCurrentTrack();
|
||||
},
|
||||
setReplayGainMode: (v) => {
|
||||
set({ replayGainMode: v });
|
||||
usePlayerStore.getState().updateReplayGainForCurrentTrack();
|
||||
updateReplayGainForCurrentTrack();
|
||||
},
|
||||
setReplayGainPreGainDb: (v) => {
|
||||
set({ replayGainPreGainDb: v });
|
||||
usePlayerStore.getState().updateReplayGainForCurrentTrack();
|
||||
updateReplayGainForCurrentTrack();
|
||||
},
|
||||
setReplayGainFallbackDb: (v) => {
|
||||
set({ replayGainFallbackDb: v });
|
||||
usePlayerStore.getState().updateReplayGainForCurrentTrack();
|
||||
updateReplayGainForCurrentTrack();
|
||||
},
|
||||
setCrossfadeEnabled: (v) => set({ crossfadeEnabled: v }),
|
||||
setCrossfadeSecs: (v) => set({ crossfadeSecs: v }),
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { AuthState } from './authStoreTypes';
|
||||
import { generateId } from './authStoreHelpers';
|
||||
import { usePlayerStore } from './playerStore';
|
||||
import { clearQueueServerForPlayback } from '../utils/playback/playbackServer';
|
||||
import { getQueueServerId, clearQueueServerForPlayback } from './playbackEngineBridge';
|
||||
import { resolveServerIdForIndexKey } from '../utils/server/serverLookup';
|
||||
|
||||
type SetState = (
|
||||
@@ -44,7 +43,7 @@ export function createServerProfileActions(set: SetState): Pick<
|
||||
// queueServerId is the canonical index key (B1); resolve the
|
||||
// canonical id back to a server UUID before comparing so a profile
|
||||
// delete still clears the matching queue binding.
|
||||
const queueSid = usePlayerStore.getState().queueServerId;
|
||||
const queueSid = getQueueServerId();
|
||||
if (queueSid && resolveServerIdForIndexKey(queueSid) === id) {
|
||||
clearQueueServerForPlayback();
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
import { beforeEach, describe, expect, it } from 'vitest';
|
||||
import { useAuthStore } from './authStore';
|
||||
import { usePlayerStore } from './playerStore';
|
||||
import './playbackEngineBridgeRegister'; // wire removeServer's queue-clear through the real engine bridge
|
||||
import { resetAuthStore } from '@/test/helpers/storeReset';
|
||||
import { resetPlayerStore } from '@/test/helpers/storeReset';
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
// Playback-engine bridge. Global settings/profile stores (authStore family) need
|
||||
// to trigger a few engine effects — clear a deleted server's queue binding, catch
|
||||
// the current track up to a changed ReplayGain/normalization mode — but those
|
||||
// stores are core and must not import the playback engine (iron rule; the engine
|
||||
// will live in @/features/playback). The engine registers its operations here at
|
||||
// boot (playbackEngineBridgeRegister.ts, side-effect-imported by MainApp); core
|
||||
// callers invoke these neutral delegators.
|
||||
//
|
||||
// Default (unregistered) = no-op / null. Safe: the only callers are user-triggered
|
||||
// settings/profile actions that fire long after boot, by which point the engine
|
||||
// (loaded at app start) has registered. At boot there is no current track or queue
|
||||
// binding, so the no-op default would be correct even if it were ever reached.
|
||||
|
||||
export interface PlaybackEngineBridge {
|
||||
/** Active queue's bound server id, or null when nothing is bound. */
|
||||
getQueueServerId(): string | null;
|
||||
/** Drop the queue's server binding (used when its server profile is deleted). */
|
||||
clearQueueServerForPlayback(): void;
|
||||
/** Re-apply ReplayGain/normalization to the currently playing track. */
|
||||
updateReplayGainForCurrentTrack(): void;
|
||||
}
|
||||
|
||||
let bridge: PlaybackEngineBridge | null = null;
|
||||
|
||||
export function registerPlaybackEngineBridge(impl: PlaybackEngineBridge): void {
|
||||
bridge = impl;
|
||||
}
|
||||
|
||||
export function getQueueServerId(): string | null {
|
||||
return bridge ? bridge.getQueueServerId() : null;
|
||||
}
|
||||
|
||||
export function clearQueueServerForPlayback(): void {
|
||||
bridge?.clearQueueServerForPlayback();
|
||||
}
|
||||
|
||||
export function updateReplayGainForCurrentTrack(): void {
|
||||
bridge?.updateReplayGainForCurrentTrack();
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// Engine-side registration for the playback-engine bridge. Side-effect module:
|
||||
// importing it installs the engine's operations into @/store/playbackEngineBridge.
|
||||
// MainApp side-effect-imports this at boot. Lives with the engine (moves into
|
||||
// @/features/playback alongside playerStore); the bridge itself stays in core.
|
||||
import { usePlayerStore } from './playerStore';
|
||||
import { clearQueueServerForPlayback } from '../utils/playback/playbackServer';
|
||||
import { registerPlaybackEngineBridge } from './playbackEngineBridge';
|
||||
|
||||
registerPlaybackEngineBridge({
|
||||
getQueueServerId: () => usePlayerStore.getState().queueServerId,
|
||||
clearQueueServerForPlayback,
|
||||
updateReplayGainForCurrentTrack: () => usePlayerStore.getState().updateReplayGainForCurrentTrack(),
|
||||
});
|
||||
Reference in New Issue
Block a user