Enhance DeviceContext to fetch and manage PEQ state; update EQPage to synchronize PEQ catalog and improve headphone selection logic based on PEQ state changes. Refactor related functions for better state handling and synchronization.
This commit is contained in:
@@ -67,6 +67,11 @@ export function DeviceProvider({ children }: { children: React.ReactNode }) {
|
||||
lastMsgCountRef.current = state.msgCount;
|
||||
}, []);
|
||||
|
||||
const fetchPeqState = useCallback(async (apiInstance: LuxsinAPI) => {
|
||||
const peq = await apiInstance.getPeqState();
|
||||
setPeqState(peq);
|
||||
}, []);
|
||||
|
||||
const connect = useCallback(async (forceDemo?: boolean, connectIp?: string) => {
|
||||
const useDemo = forceDemo !== undefined ? forceDemo : isDemoMode;
|
||||
const targetIp = (connectIp ?? ip).trim();
|
||||
@@ -138,10 +143,11 @@ export function DeviceProvider({ children }: { children: React.ReactNode }) {
|
||||
if (!apiInstance) return;
|
||||
try {
|
||||
await fetchState(apiInstance);
|
||||
await fetchPeqState(apiInstance).catch(() => undefined);
|
||||
} catch (e) {
|
||||
setError(`Refresh failed: ${e instanceof Error ? e.message : String(e)}`);
|
||||
}
|
||||
}, [api, isConnected, ip, isDemoMode, fetchState]);
|
||||
}, [api, isConnected, ip, isDemoMode, fetchState, fetchPeqState]);
|
||||
|
||||
const updateSetting = useCallback(async (params: Record<string, string | number>) => {
|
||||
if (isDemoMode) {
|
||||
@@ -235,7 +241,10 @@ export function DeviceProvider({ children }: { children: React.ReactNode }) {
|
||||
try {
|
||||
const count = await api.getMsgCount();
|
||||
if (count !== lastMsgCountRef.current) {
|
||||
await fetchState(api);
|
||||
await Promise.all([
|
||||
fetchState(api),
|
||||
fetchPeqState(api).catch(() => undefined),
|
||||
]);
|
||||
}
|
||||
} catch {
|
||||
// Silently fail polling
|
||||
@@ -246,7 +255,7 @@ export function DeviceProvider({ children }: { children: React.ReactNode }) {
|
||||
return () => {
|
||||
if (pollIntervalRef.current) clearInterval(pollIntervalRef.current);
|
||||
};
|
||||
}, [isConnected, isDemoMode, api, fetchState]);
|
||||
}, [isConnected, isDemoMode, api, fetchState, fetchPeqState]);
|
||||
|
||||
return (
|
||||
<DeviceContext.Provider value={{
|
||||
|
||||
+66
-21
@@ -32,6 +32,7 @@ import {
|
||||
type PeqApplyPayload,
|
||||
type PeqChangePayload,
|
||||
type PeqPresetBody,
|
||||
type PeqState,
|
||||
} from "@/lib/luxsinApi";
|
||||
import * as echarts from "echarts";
|
||||
import {
|
||||
@@ -607,6 +608,18 @@ function freqLabel(f: number) {
|
||||
return f >= 1000 ? `${(f / 1000).toFixed(1)}K` : `${f}`;
|
||||
}
|
||||
|
||||
type PeqCatalogItem = NonNullable<PeqState["peq"]>[number];
|
||||
|
||||
function buildPeqCatalogSyncKey(
|
||||
remote: Pick<PeqState, "peq" | "peqSelect"> | null | undefined,
|
||||
devicePeqSelect?: number,
|
||||
): string {
|
||||
const items = remote?.peq;
|
||||
if (!items?.length) return items ? "empty" : "";
|
||||
const select = devicePeqSelect ?? remote?.peqSelect ?? 0;
|
||||
return `${select}|${items.map((p) => p.name).join("\u0001")}`;
|
||||
}
|
||||
|
||||
function getUniquePresetName(base: string, existingNames: string[]) {
|
||||
if (!existingNames.includes(base)) return base;
|
||||
let index = 1;
|
||||
@@ -665,7 +678,8 @@ function bandToPeqFilter(b: { freq: number; gain: number; q: number; type: strin
|
||||
/* ── Main Component ── */
|
||||
export default function EQPage() {
|
||||
const [, setLocation] = useLocation();
|
||||
const { deviceState, updateSetting, api, isDemoMode, upgradePeqChange, upgradePeqApply } = useDevice();
|
||||
const { deviceState, peqState, updateSetting, api, isDemoMode, upgradePeqChange, upgradePeqApply } =
|
||||
useDevice();
|
||||
const bypassOn = (deviceState?.dsp_enable ?? 0) === 0;
|
||||
const peqOn = (deviceState?.peqEnable ?? 0) === 1;
|
||||
const eqOn = peqOn && !bypassOn;
|
||||
@@ -738,6 +752,7 @@ export default function EQPage() {
|
||||
const [isConfirmingTarget, setIsConfirmingTarget] = useState(false);
|
||||
const [currentRawCurve, setCurrentRawCurve] = useState<number[] | null>(null);
|
||||
const allowPeqRemoteSyncRef = useRef(false);
|
||||
const lastPeqCatalogSyncKeyRef = useRef("");
|
||||
const syncingHeadphoneRef = useRef(false);
|
||||
const peqSyncTimerRef = useRef<number | null>(null);
|
||||
const skipPeqAutoSyncRef = useRef(false);
|
||||
@@ -1039,22 +1054,44 @@ export default function EQPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const applyPeqStateToUI = (
|
||||
peqState: { peq?: Array<{ name: string; filters?: any[] | string }>; peqSelect?: number },
|
||||
) => {
|
||||
const items = peqState.peq ?? [];
|
||||
setPeqItems(items as typeof peqItems);
|
||||
setHeadphoneModels(items.map((item) => item.name));
|
||||
const syncPeqCatalogFromState = useCallback(
|
||||
(
|
||||
remote: { peq?: PeqCatalogItem[]; peqSelect?: number },
|
||||
devicePeqSelect?: number,
|
||||
) => {
|
||||
const items = remote.peq ?? [];
|
||||
setPeqItems(items as typeof peqItems);
|
||||
setHeadphoneModels(items.map((item) => item.name));
|
||||
if (items.length === 0) {
|
||||
setHeadphoneIdx(0);
|
||||
return;
|
||||
}
|
||||
const nextIdx = Math.min(
|
||||
Math.max(devicePeqSelect ?? remote.peqSelect ?? 0, 0),
|
||||
items.length - 1,
|
||||
);
|
||||
setHeadphoneIdx(nextIdx);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const applyPeqStateToUI = (
|
||||
remote: { peq?: Array<{ name: string; filters?: any[] | string }>; peqSelect?: number },
|
||||
) => {
|
||||
lastPeqCatalogSyncKeyRef.current = buildPeqCatalogSyncKey(remote, deviceState?.peqSelect);
|
||||
syncPeqCatalogFromState(remote, deviceState?.peqSelect);
|
||||
|
||||
const items = remote.peq ?? [];
|
||||
if (items.length === 0) {
|
||||
setHeadphoneIdx(0);
|
||||
setBandsForBothModes(DEFAULT_BANDS);
|
||||
setSelectedBandByMode({ A: 0, B: 0 });
|
||||
return;
|
||||
}
|
||||
|
||||
const nextIdx = Math.min(Math.max(peqState.peqSelect ?? 0, 0), items.length - 1);
|
||||
setHeadphoneIdx(nextIdx);
|
||||
const nextIdx = Math.min(
|
||||
Math.max(deviceState?.peqSelect ?? remote.peqSelect ?? 0, 0),
|
||||
items.length - 1,
|
||||
);
|
||||
const nextBands = normalizeFiltersFromPeq(items[nextIdx] as { filters?: any[] | string });
|
||||
if (nextBands.length > 0) {
|
||||
setBandsForBothModes(nextBands);
|
||||
@@ -1223,12 +1260,14 @@ export default function EQPage() {
|
||||
}
|
||||
};
|
||||
|
||||
// 同步 peqSelect 变化
|
||||
// msgCount 轮询更新 peqState 后,同步耳机列表与当前选中项
|
||||
useEffect(() => {
|
||||
if (deviceState?.peqSelect !== undefined) {
|
||||
setHeadphoneIdx(deviceState.peqSelect);
|
||||
}
|
||||
}, [deviceState?.peqSelect]);
|
||||
if (!peqState?.peq) return;
|
||||
const key = buildPeqCatalogSyncKey(peqState, deviceState?.peqSelect);
|
||||
if (!key || key === lastPeqCatalogSyncKeyRef.current) return;
|
||||
lastPeqCatalogSyncKeyRef.current = key;
|
||||
syncPeqCatalogFromState(peqState, deviceState?.peqSelect);
|
||||
}, [peqState, deviceState?.peqSelect, syncPeqCatalogFromState]);
|
||||
|
||||
// 切换耳机时默认回到 A 组对比
|
||||
useEffect(() => {
|
||||
@@ -1335,13 +1374,19 @@ export default function EQPage() {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const peqState = await api.getPeqState();
|
||||
if (peqState.peq && peqState.peq.length > 0) {
|
||||
setHeadphoneModels(peqState.peq.map(h => h.name));
|
||||
setPeqItems(peqState.peq);
|
||||
|
||||
const loadedPeq = await api.getPeqState();
|
||||
if (loadedPeq.peq && loadedPeq.peq.length > 0) {
|
||||
setHeadphoneModels(loadedPeq.peq.map((h) => h.name));
|
||||
setPeqItems(loadedPeq.peq);
|
||||
const nextIdx = Math.min(
|
||||
Math.max(deviceState?.peqSelect ?? loadedPeq.peqSelect ?? 0, 0),
|
||||
loadedPeq.peq.length - 1,
|
||||
);
|
||||
setHeadphoneIdx(nextIdx);
|
||||
lastPeqCatalogSyncKeyRef.current = buildPeqCatalogSyncKey(loadedPeq, deviceState?.peqSelect);
|
||||
|
||||
// 初始化当前选中的耳机曲线
|
||||
const currentPeq = peqState.peq[peqState.peqSelect || 0];
|
||||
const currentPeq = loadedPeq.peq[nextIdx];
|
||||
if (currentPeq) {
|
||||
// 修复 filters 数据(兼容不同的字段名)
|
||||
const fixedFilters = normalizeFiltersFromPeq(currentPeq);
|
||||
|
||||
Reference in New Issue
Block a user