修复 bug:EQ 页面调节任意参数后,耳机会自动切换到别的耳机
This commit is contained in:
+63
-29
@@ -205,6 +205,10 @@ export default function EQPage() {
|
||||
const syncingHeadphoneRef = useRef(false);
|
||||
const peqSyncTimerRef = useRef<number | null>(null);
|
||||
const peqPresetCacheRef = useRef<Record<string, PeqPresetLocalCache>>({});
|
||||
/** Anchor UI selection by preset name so poll/catalog reorder won't mis-switch headphones. */
|
||||
const selectedPeqNameRef = useRef("");
|
||||
const devicePeqSelectRef = useRef(deviceState?.peqSelect);
|
||||
devicePeqSelectRef.current = deviceState?.peqSelect;
|
||||
const skipPeqAutoSyncRef = useRef(false);
|
||||
const skipBandsSyncFromAbToggleRef = useRef(false);
|
||||
const headphoneMenuRef = useRef<HTMLDivElement | null>(null);
|
||||
@@ -502,6 +506,11 @@ export default function EQPage() {
|
||||
});
|
||||
}, []);
|
||||
|
||||
const selectHeadphone = useCallback((idx: number, name?: string) => {
|
||||
selectedPeqNameRef.current = name ?? "";
|
||||
setHeadphoneIdx(idx);
|
||||
}, []);
|
||||
|
||||
const syncPeqCatalogFromState = useCallback(
|
||||
(
|
||||
remote: { peq?: PeqCatalogItem[]; peqSelect?: number },
|
||||
@@ -511,14 +520,24 @@ export default function EQPage() {
|
||||
setPeqItems(items as typeof peqItems);
|
||||
setHeadphoneModels(items.map(item => item.name));
|
||||
if (items.length === 0) {
|
||||
selectedPeqNameRef.current = "";
|
||||
setHeadphoneIdx(0);
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
const nextIdx = Math.min(
|
||||
Math.max(devicePeqSelect ?? remote.peqSelect ?? 0, 0),
|
||||
items.length - 1
|
||||
);
|
||||
const preferred = selectedPeqNameRef.current;
|
||||
let nextIdx =
|
||||
preferred !== ""
|
||||
? items.findIndex(item => item.name === preferred)
|
||||
: -1;
|
||||
if (nextIdx < 0) {
|
||||
nextIdx = Math.min(
|
||||
Math.max(devicePeqSelect ?? remote.peqSelect ?? 0, 0),
|
||||
items.length - 1
|
||||
);
|
||||
}
|
||||
selectedPeqNameRef.current = items[nextIdx]?.name ?? "";
|
||||
setHeadphoneIdx(nextIdx);
|
||||
return nextIdx;
|
||||
},
|
||||
[mergeRemotePeqCatalog]
|
||||
);
|
||||
@@ -542,7 +561,7 @@ export default function EQPage() {
|
||||
remote,
|
||||
resolvedPeqSelect
|
||||
);
|
||||
syncPeqCatalogFromState(remote, resolvedPeqSelect);
|
||||
const catalogIdx = syncPeqCatalogFromState(remote, resolvedPeqSelect);
|
||||
|
||||
const items = remote.peq ?? [];
|
||||
if (items.length === 0) {
|
||||
@@ -551,12 +570,8 @@ export default function EQPage() {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextIdx = Math.min(
|
||||
Math.max(resolvedPeqSelect, 0),
|
||||
items.length - 1
|
||||
);
|
||||
const nextBands = normalizeFiltersFromPeq(
|
||||
items[nextIdx] as { filters?: any[] | string }
|
||||
items[catalogIdx] as { filters?: any[] | string }
|
||||
);
|
||||
if (nextBands.length > 0) {
|
||||
setBandsForBothModes(nextBands);
|
||||
@@ -676,7 +691,7 @@ export default function EQPage() {
|
||||
const createdIndex =
|
||||
latest.peq?.findIndex(item => item.name === nextName) ?? -1;
|
||||
if (createdIndex >= 0) {
|
||||
setHeadphoneIdx(createdIndex);
|
||||
selectHeadphone(createdIndex, nextName);
|
||||
updateSetting({ peqSelect: createdIndex });
|
||||
}
|
||||
} else {
|
||||
@@ -709,7 +724,7 @@ export default function EQPage() {
|
||||
canDel: 1,
|
||||
},
|
||||
]);
|
||||
setHeadphoneIdx(headphoneModels.length);
|
||||
selectHeadphone(headphoneModels.length, nextName);
|
||||
}
|
||||
setIsAddPresetDialogOpen(false);
|
||||
toast.success(eqUi.toastAddPresetOk);
|
||||
@@ -764,7 +779,7 @@ export default function EQPage() {
|
||||
const createdIndex =
|
||||
latest.peq?.findIndex(item => item.name === nextName) ?? -1;
|
||||
if (createdIndex >= 0) {
|
||||
setHeadphoneIdx(createdIndex);
|
||||
selectHeadphone(createdIndex, nextName);
|
||||
updateSetting({ peqSelect: createdIndex });
|
||||
}
|
||||
} else {
|
||||
@@ -779,7 +794,7 @@ export default function EQPage() {
|
||||
canDel: currentPeq?.canDel ?? 1,
|
||||
},
|
||||
]);
|
||||
setHeadphoneIdx(headphoneModels.length);
|
||||
selectHeadphone(headphoneModels.length, nextName);
|
||||
}
|
||||
setIsSaveBDialogOpen(false);
|
||||
toast.success(eqUi.toastSaveBOk);
|
||||
@@ -856,7 +871,7 @@ export default function EQPage() {
|
||||
const targetIdx =
|
||||
latest.peq?.findIndex(item => item.name === nextName) ?? -1;
|
||||
if (targetIdx >= 0) {
|
||||
setHeadphoneIdx(targetIdx);
|
||||
selectHeadphone(targetIdx, nextName);
|
||||
updateSetting({ peqSelect: targetIdx });
|
||||
}
|
||||
} else if (isOverwrite) {
|
||||
@@ -878,7 +893,7 @@ export default function EQPage() {
|
||||
: item
|
||||
)
|
||||
);
|
||||
setHeadphoneIdx(existingIdx);
|
||||
selectHeadphone(existingIdx, nextName);
|
||||
} else {
|
||||
setHeadphoneModels(prev => [...prev, nextName]);
|
||||
setPeqItems(prev => [
|
||||
@@ -895,7 +910,7 @@ export default function EQPage() {
|
||||
canDel: 1,
|
||||
},
|
||||
]);
|
||||
setHeadphoneIdx(headphoneModels.length);
|
||||
selectHeadphone(headphoneModels.length, nextName);
|
||||
}
|
||||
toast.success(eqUi.importSuccess);
|
||||
setIsShareDialogOpen(false);
|
||||
@@ -925,11 +940,14 @@ export default function EQPage() {
|
||||
|
||||
// 加载耳机列表并初始化曲线
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
|
||||
async function loadHeadphones() {
|
||||
peqHydrationPendingRef.current = true;
|
||||
allowPeqRemoteSyncRef.current = false;
|
||||
try {
|
||||
if (isDemoMode || !api) {
|
||||
if (cancelled) return;
|
||||
// 演示模式使用默认列表
|
||||
const defaultModels = [
|
||||
"Sennheiser HD 650",
|
||||
@@ -941,23 +959,28 @@ export default function EQPage() {
|
||||
];
|
||||
setHeadphoneModels(defaultModels);
|
||||
setPeqItems(defaultModels.map(name => ({ name, filters: [] })));
|
||||
selectHeadphone(0, defaultModels[0]);
|
||||
setCurrentRawCurve(null);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const loadedPeq = await fetchEqSyncPeq(api, "loadHeadphones");
|
||||
if (cancelled) return;
|
||||
if (loadedPeq.peq && loadedPeq.peq.length > 0) {
|
||||
const mergedPeq = mergeRemotePeqCatalog(loadedPeq.peq);
|
||||
setHeadphoneModels(mergedPeq.map(h => h.name));
|
||||
setPeqItems(mergedPeq);
|
||||
const nextIdx = Math.min(
|
||||
Math.max(deviceState?.peqSelect ?? loadedPeq.peqSelect ?? 0, 0),
|
||||
Math.max(
|
||||
devicePeqSelectRef.current ?? loadedPeq.peqSelect ?? 0,
|
||||
0
|
||||
),
|
||||
mergedPeq.length - 1
|
||||
);
|
||||
setHeadphoneIdx(nextIdx);
|
||||
selectHeadphone(nextIdx, mergedPeq[nextIdx]?.name);
|
||||
lastPeqCatalogSyncKeyRef.current = buildPeqCatalogSyncKey(
|
||||
loadedPeq,
|
||||
deviceState?.peqSelect
|
||||
devicePeqSelectRef.current ?? loadedPeq.peqSelect
|
||||
);
|
||||
|
||||
// 初始化当前选中的耳机曲线
|
||||
@@ -987,9 +1010,11 @@ export default function EQPage() {
|
||||
];
|
||||
setHeadphoneModels(defaultModels);
|
||||
setPeqItems(defaultModels.map(name => ({ name, filters: [] })));
|
||||
selectHeadphone(0, defaultModels[0]);
|
||||
setCurrentRawCurve(null);
|
||||
}
|
||||
} catch {
|
||||
if (cancelled) return;
|
||||
// 出错时使用默认列表
|
||||
const defaultModels = [
|
||||
"Sennheiser HD 650",
|
||||
@@ -1001,23 +1026,28 @@ export default function EQPage() {
|
||||
];
|
||||
setHeadphoneModels(defaultModels);
|
||||
setPeqItems(defaultModels.map(name => ({ name, filters: [] })));
|
||||
selectHeadphone(0, defaultModels[0]);
|
||||
setCurrentRawCurve(null);
|
||||
}
|
||||
} finally {
|
||||
if (cancelled) return;
|
||||
allowPeqRemoteSyncRef.current = true;
|
||||
skipPeqAutoSyncRef.current = true;
|
||||
queueMicrotask(() => {
|
||||
peqHydrationPendingRef.current = false;
|
||||
if (!cancelled) peqHydrationPendingRef.current = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
loadHeadphones();
|
||||
void loadHeadphones();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [
|
||||
api,
|
||||
isDemoMode,
|
||||
loadRawCurveForPeq,
|
||||
mergeRemotePeqCatalog,
|
||||
deviceState?.peqSelect,
|
||||
selectHeadphone,
|
||||
setCurrentRawCurve,
|
||||
]);
|
||||
|
||||
/** 切换耳机型号时变;不含 filters,避免编辑写回 peqItems 后反复加载 bands 并触发 peqChange 循环 */
|
||||
@@ -1122,7 +1152,7 @@ export default function EQPage() {
|
||||
window.clearTimeout(peqSyncTimerRef.current);
|
||||
peqSyncTimerRef.current = null;
|
||||
}
|
||||
setHeadphoneIdx(idx);
|
||||
selectHeadphone(idx, peqItems[idx]?.name);
|
||||
updateSetting({ peqSelect: idx });
|
||||
setIsHeadphoneMenuOpen(false);
|
||||
},
|
||||
@@ -1132,6 +1162,7 @@ export default function EQPage() {
|
||||
peqItems,
|
||||
persistPresetEditsToPeqItem,
|
||||
rememberPeqPresetCache,
|
||||
selectHeadphone,
|
||||
updateSetting,
|
||||
]
|
||||
);
|
||||
@@ -2007,6 +2038,9 @@ export default function EQPage() {
|
||||
}}
|
||||
onRenamePreset={async (oldName, newName, item) => {
|
||||
try {
|
||||
if (selectedPeqNameRef.current === oldName) {
|
||||
selectedPeqNameRef.current = newName;
|
||||
}
|
||||
if (isDemoMode || !api) {
|
||||
const nextItems = peqItems.map(it =>
|
||||
it.name === oldName ? { ...it, name: newName } : it
|
||||
@@ -2164,7 +2198,7 @@ export default function EQPage() {
|
||||
latest.peq?.findIndex(item => item.name === createdName) ??
|
||||
-1;
|
||||
if (targetIdx >= 0) {
|
||||
setHeadphoneIdx(targetIdx);
|
||||
selectHeadphone(targetIdx, createdName);
|
||||
updateSetting({ peqSelect: targetIdx });
|
||||
}
|
||||
} else if (isOverwrite) {
|
||||
@@ -2186,7 +2220,7 @@ export default function EQPage() {
|
||||
: item
|
||||
)
|
||||
);
|
||||
setHeadphoneIdx(existingIdx);
|
||||
selectHeadphone(existingIdx, createdName);
|
||||
} else {
|
||||
let nextIndex = 0;
|
||||
setPeqItems(prev => [
|
||||
@@ -2207,7 +2241,7 @@ export default function EQPage() {
|
||||
nextIndex = prev.length;
|
||||
return [...prev, createdName];
|
||||
});
|
||||
setHeadphoneIdx(nextIndex);
|
||||
selectHeadphone(nextIndex, createdName);
|
||||
}
|
||||
setIsBrandDrawerOpen(false);
|
||||
} catch {
|
||||
|
||||
Reference in New Issue
Block a user