修复 bug:EQ 页面调节任意参数后,耳机会自动切换到别的耳机

This commit is contained in:
eafonyang
2026-08-06 11:09:33 +08:00
parent 0c2dcc9a10
commit 2cd61ea7b5
+124 -85
View File
@@ -190,6 +190,10 @@ export default function EQPage() {
const skipBandsSyncFromAbToggleRef = useRef(false); const skipBandsSyncFromAbToggleRef = useRef(false);
/** 本地编辑过的预设(filters / preamp / autoPre);syncPeq 拉取不会覆盖 */ /** 本地编辑过的预设(filters / preamp / autoPre);syncPeq 拉取不会覆盖 */
const peqPresetCacheRef = useRef<Record<string, PeqPresetLocalCache>>({}); 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 headphoneMenuRef = useRef<HTMLDivElement | null>(null); const headphoneMenuRef = useRef<HTMLDivElement | null>(null);
const filterMenuRef = useRef<HTMLDivElement | null>(null); const filterMenuRef = useRef<HTMLDivElement | null>(null);
const bands = bandsByMode[abMode]; const bands = bandsByMode[abMode];
@@ -446,6 +450,11 @@ export default function EQPage() {
}); });
}, []); }, []);
const selectHeadphone = useCallback((idx: number, name?: string) => {
selectedPeqNameRef.current = name ?? "";
setHeadphoneIdx(idx);
}, []);
const syncPeqCatalogFromState = useCallback( const syncPeqCatalogFromState = useCallback(
( (
remote: { peq?: PeqCatalogItem[]; peqSelect?: number }, remote: { peq?: PeqCatalogItem[]; peqSelect?: number },
@@ -455,14 +464,24 @@ export default function EQPage() {
setPeqItems(items as typeof peqItems); setPeqItems(items as typeof peqItems);
setHeadphoneModels(items.map((item) => item.name)); setHeadphoneModels(items.map((item) => item.name));
if (items.length === 0) { if (items.length === 0) {
selectedPeqNameRef.current = "";
setHeadphoneIdx(0); setHeadphoneIdx(0);
return; return 0;
} }
const nextIdx = Math.min( const preferred = selectedPeqNameRef.current;
Math.max(devicePeqSelect ?? remote.peqSelect ?? 0, 0), let nextIdx =
items.length - 1, 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); setHeadphoneIdx(nextIdx);
return nextIdx;
}, },
[mergeRemotePeqCatalog], [mergeRemotePeqCatalog],
); );
@@ -517,7 +536,7 @@ export default function EQPage() {
window.clearTimeout(peqSyncTimerRef.current); window.clearTimeout(peqSyncTimerRef.current);
peqSyncTimerRef.current = null; peqSyncTimerRef.current = null;
} }
setHeadphoneIdx(idx); selectHeadphone(idx, peqItems[idx]?.name);
updateSetting({ peqSelect: idx }); updateSetting({ peqSelect: idx });
setIsHeadphoneMenuOpen(false); setIsHeadphoneMenuOpen(false);
}, },
@@ -527,6 +546,7 @@ export default function EQPage() {
peqItems, peqItems,
persistPresetEditsToPeqItem, persistPresetEditsToPeqItem,
rememberPeqPresetCache, rememberPeqPresetCache,
selectHeadphone,
updateSetting, updateSetting,
], ],
); );
@@ -545,20 +565,18 @@ export default function EQPage() {
}); });
} }
lastPeqCatalogSyncKeyRef.current = buildPeqCatalogSyncKey(remote, resolvedPeqSelect); lastPeqCatalogSyncKeyRef.current = buildPeqCatalogSyncKey(remote, resolvedPeqSelect);
syncPeqCatalogFromState(remote, resolvedPeqSelect); const catalogIdx = syncPeqCatalogFromState(remote, resolvedPeqSelect);
const items = mergeRemotePeqCatalog((remote.peq ?? []) as PeqCatalogItem[]); const items = remote.peq ?? [];
if (items.length === 0) { if (items.length === 0) {
setBandsForBothModes(DEFAULT_BANDS); setBandsForBothModes(DEFAULT_BANDS);
setSelectedBandByMode({ A: 0, B: 0 }); setSelectedBandByMode({ A: 0, B: 0 });
return; return;
} }
const nextIdx = Math.min( const nextBands = normalizeFiltersFromPeq(
Math.max(resolvedPeqSelect, 0), items[catalogIdx] as { filters?: any[] | string },
items.length - 1,
); );
const nextBands = normalizeFiltersFromPeq(items[nextIdx] as { filters?: any[] | string });
if (nextBands.length > 0) { if (nextBands.length > 0) {
setBandsForBothModes(nextBands); setBandsForBothModes(nextBands);
} }
@@ -664,7 +682,7 @@ export default function EQPage() {
applyPeqStateToUI(latest as { peq?: Array<{ name: string; filters?: any[] | string }>; peqSelect?: number }); applyPeqStateToUI(latest as { peq?: Array<{ name: string; filters?: any[] | string }>; peqSelect?: number });
const createdIndex = latest.peq?.findIndex((item) => item.name === nextName) ?? -1; const createdIndex = latest.peq?.findIndex((item) => item.name === nextName) ?? -1;
if (createdIndex >= 0) { if (createdIndex >= 0) {
setHeadphoneIdx(createdIndex); selectHeadphone(createdIndex, nextName);
updateSetting({ peqSelect: createdIndex }); updateSetting({ peqSelect: createdIndex });
} }
} else { } else {
@@ -688,7 +706,7 @@ export default function EQPage() {
canDel: 1, canDel: 1,
}, },
]); ]);
setHeadphoneIdx(headphoneModels.length); selectHeadphone(headphoneModels.length, nextName);
} }
setIsAddPresetDialogOpen(false); setIsAddPresetDialogOpen(false);
toast.success(eqUi.toastAddPresetOk); toast.success(eqUi.toastAddPresetOk);
@@ -737,7 +755,7 @@ export default function EQPage() {
applyPeqStateToUI(latest as { peq?: Array<{ name: string; filters?: any[] | string }>; peqSelect?: number }); applyPeqStateToUI(latest as { peq?: Array<{ name: string; filters?: any[] | string }>; peqSelect?: number });
const createdIndex = latest.peq?.findIndex((item) => item.name === nextName) ?? -1; const createdIndex = latest.peq?.findIndex((item) => item.name === nextName) ?? -1;
if (createdIndex >= 0) { if (createdIndex >= 0) {
setHeadphoneIdx(createdIndex); selectHeadphone(createdIndex, nextName);
updateSetting({ peqSelect: createdIndex }); updateSetting({ peqSelect: createdIndex });
} }
} else { } else {
@@ -752,7 +770,7 @@ export default function EQPage() {
canDel: currentPeq?.canDel ?? 1, canDel: currentPeq?.canDel ?? 1,
}, },
]); ]);
setHeadphoneIdx(headphoneModels.length); selectHeadphone(headphoneModels.length, nextName);
} }
setIsSaveBDialogOpen(false); setIsSaveBDialogOpen(false);
toast.success(eqUi.toastSaveBOk); toast.success(eqUi.toastSaveBOk);
@@ -822,7 +840,7 @@ export default function EQPage() {
applyPeqStateToUI(latest as { peq?: Array<{ name: string; filters?: any[] | string }>; peqSelect?: number }); applyPeqStateToUI(latest as { peq?: Array<{ name: string; filters?: any[] | string }>; peqSelect?: number });
const targetIdx = latest.peq?.findIndex((item) => item.name === nextName) ?? -1; const targetIdx = latest.peq?.findIndex((item) => item.name === nextName) ?? -1;
if (targetIdx >= 0) { if (targetIdx >= 0) {
setHeadphoneIdx(targetIdx); selectHeadphone(targetIdx, nextName);
updateSetting({ peqSelect: targetIdx }); updateSetting({ peqSelect: targetIdx });
} }
} else if (isOverwrite) { } else if (isOverwrite) {
@@ -844,7 +862,7 @@ export default function EQPage() {
: item, : item,
), ),
); );
setHeadphoneIdx(existingIdx); selectHeadphone(existingIdx, nextName);
} else { } else {
setHeadphoneModels((prev) => [...prev, nextName]); setHeadphoneModels((prev) => [...prev, nextName]);
setPeqItems((prev) => [ setPeqItems((prev) => [
@@ -861,7 +879,7 @@ export default function EQPage() {
canDel: 1, canDel: 1,
}, },
]); ]);
setHeadphoneIdx(headphoneModels.length); selectHeadphone(headphoneModels.length, nextName);
} }
toast.success(eqUi.importSuccess); toast.success(eqUi.importSuccess);
setIsShareDialogOpen(false); setIsShareDialogOpen(false);
@@ -891,56 +909,15 @@ export default function EQPage() {
// 加载耳机列表并初始化曲线 // 加载耳机列表并初始化曲线
useEffect(() => { useEffect(() => {
let cancelled = false;
async function loadHeadphones() { async function loadHeadphones() {
peqHydrationPendingRef.current = true; peqHydrationPendingRef.current = true;
allowPeqRemoteSyncRef.current = false; allowPeqRemoteSyncRef.current = false;
try { try {
if (isDemoMode || !api) { if (isDemoMode || !api) {
// 演示模式使用默认列表 if (cancelled) return;
const defaultModels = [ // 演示模式使用默认列表
"Sennheiser HD 650",
"Sennheiser HD 800",
"Sony WH-1000XM5",
"AKG K701",
"Beyerdynamic DT 990",
"Audio-Technica ATH-M50x",
];
setHeadphoneModels(defaultModels);
setPeqItems(defaultModels.map(name => ({ name, filters: [] })));
setCurrentRawCurve(null);
return;
}
try {
const loadedPeq = await fetchEqSyncPeq(api, "loadHeadphones");
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),
mergedPeq.length - 1,
);
setHeadphoneIdx(nextIdx);
lastPeqCatalogSyncKeyRef.current = buildPeqCatalogSyncKey(loadedPeq, deviceState?.peqSelect);
// 初始化当前选中的耳机曲线
const currentPeq = mergedPeq[nextIdx];
if (currentPeq) {
// 修复 filters 数据(兼容不同的字段名)
const fixedFilters = normalizeFiltersFromPeq(currentPeq);
// 更新 bands 状态
if (fixedFilters && fixedFilters.length > 0) {
setBandsForBothModes(fixedFilters);
setSelectedBandByMode({ A: 0, B: 0 });
}
// raw 与图表渲染统一交给 headphoneIdx/peqItems 监听逻辑处理,
// 避免首次进入页面时重复请求 modelCurve。
}
} else {
// 如果没有数据,使用默认列表
const defaultModels = [ const defaultModels = [
"Sennheiser HD 650", "Sennheiser HD 650",
"Sennheiser HD 800", "Sennheiser HD 800",
@@ -951,32 +928,91 @@ export default function EQPage() {
]; ];
setHeadphoneModels(defaultModels); setHeadphoneModels(defaultModels);
setPeqItems(defaultModels.map(name => ({ name, filters: [] }))); 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(
devicePeqSelectRef.current ?? loadedPeq.peqSelect ?? 0,
0,
),
mergedPeq.length - 1,
);
selectHeadphone(nextIdx, mergedPeq[nextIdx]?.name);
lastPeqCatalogSyncKeyRef.current = buildPeqCatalogSyncKey(
loadedPeq,
devicePeqSelectRef.current ?? loadedPeq.peqSelect,
);
// 初始化当前选中的耳机曲线
const currentPeq = mergedPeq[nextIdx];
if (currentPeq) {
// 修复 filters 数据(兼容不同的字段名)
const fixedFilters = normalizeFiltersFromPeq(currentPeq);
// 更新 bands 状态
if (fixedFilters && fixedFilters.length > 0) {
setBandsForBothModes(fixedFilters);
setSelectedBandByMode({ A: 0, B: 0 });
}
// raw 与图表渲染统一交给 headphoneIdx/peqItems 监听逻辑处理,
// 避免首次进入页面时重复请求 modelCurve。
}
} else {
// 如果没有数据,使用默认列表
const defaultModels = [
"Sennheiser HD 650",
"Sennheiser HD 800",
"Sony WH-1000XM5",
"AKG K701",
"Beyerdynamic DT 990",
"Audio-Technica ATH-M50x",
];
setHeadphoneModels(defaultModels);
setPeqItems(defaultModels.map(name => ({ name, filters: [] })));
selectHeadphone(0, defaultModels[0]);
setCurrentRawCurve(null);
}
} catch {
if (cancelled) return;
// 出错时使用默认列表
const defaultModels = [
"Sennheiser HD 650",
"Sennheiser HD 800",
"Sony WH-1000XM5",
"AKG K701",
"Beyerdynamic DT 990",
"Audio-Technica ATH-M50x",
];
setHeadphoneModels(defaultModels);
setPeqItems(defaultModels.map(name => ({ name, filters: [] })));
selectHeadphone(0, defaultModels[0]);
setCurrentRawCurve(null); setCurrentRawCurve(null);
} }
} catch {
// 出错时使用默认列表
const defaultModels = [
"Sennheiser HD 650",
"Sennheiser HD 800",
"Sony WH-1000XM5",
"AKG K701",
"Beyerdynamic DT 990",
"Audio-Technica ATH-M50x",
];
setHeadphoneModels(defaultModels);
setPeqItems(defaultModels.map(name => ({ name, filters: [] })));
setCurrentRawCurve(null);
}
} finally { } finally {
if (cancelled) return;
allowPeqRemoteSyncRef.current = true; allowPeqRemoteSyncRef.current = true;
skipPeqAutoSyncRef.current = true; skipPeqAutoSyncRef.current = true;
queueMicrotask(() => { queueMicrotask(() => {
peqHydrationPendingRef.current = false; if (!cancelled) peqHydrationPendingRef.current = false;
}); });
} }
} }
loadHeadphones(); void loadHeadphones();
}, [api, isDemoMode, loadRawCurveForPeq, mergeRemotePeqCatalog, deviceState?.peqSelect]); return () => {
cancelled = true;
};
}, [api, isDemoMode, mergeRemotePeqCatalog, selectHeadphone, setCurrentRawCurve]);
/** 切换耳机型号时变;不含 filters,避免编辑时写回 peqItems 把 B 试听曲线重置 */ /** 切换耳机型号时变;不含 filters,避免编辑时写回 peqItems 把 B 试听曲线重置 */
const headphoneSwitchKey = useMemo(() => { const headphoneSwitchKey = useMemo(() => {
@@ -1786,7 +1822,7 @@ export default function EQPage() {
applyPeqStateToUI(latest as { peq?: Array<{ name: string; filters?: any[] | string }>; peqSelect?: number }); applyPeqStateToUI(latest as { peq?: Array<{ name: string; filters?: any[] | string }>; peqSelect?: number });
const targetIdx = latest.peq?.findIndex((item) => item.name === createdName) ?? -1; const targetIdx = latest.peq?.findIndex((item) => item.name === createdName) ?? -1;
if (targetIdx >= 0) { if (targetIdx >= 0) {
setHeadphoneIdx(targetIdx); selectHeadphone(targetIdx, createdName);
updateSetting({ peqSelect: targetIdx }); updateSetting({ peqSelect: targetIdx });
} }
} else if (isOverwrite) { } else if (isOverwrite) {
@@ -1808,7 +1844,7 @@ export default function EQPage() {
: item, : item,
), ),
); );
setHeadphoneIdx(existingIdx); selectHeadphone(existingIdx, createdName);
} else { } else {
let nextIndex = 0; let nextIndex = 0;
setPeqItems((prev) => [ setPeqItems((prev) => [
@@ -1829,7 +1865,7 @@ export default function EQPage() {
nextIndex = prev.length; nextIndex = prev.length;
return [...prev, createdName]; return [...prev, createdName];
}); });
setHeadphoneIdx(nextIndex); selectHeadphone(nextIndex, createdName);
} }
setIsBrandDrawerOpen(false); setIsBrandDrawerOpen(false);
} catch { } catch {
@@ -1909,6 +1945,9 @@ export default function EQPage() {
}} }}
onRenamePreset={async (oldName, newName, item) => { onRenamePreset={async (oldName, newName, item) => {
try { try {
if (selectedPeqNameRef.current === oldName) {
selectedPeqNameRef.current = newName;
}
if (isDemoMode || !api) { if (isDemoMode || !api) {
const nextItems = peqItems.map(it => const nextItems = peqItems.map(it =>
it.name === oldName ? { ...it, name: newName } : it it.name === oldName ? { ...it, name: newName } : it