x9适配
This commit is contained in:
+100
-22
@@ -22,7 +22,7 @@ import {
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from "@/components/ui/alert-dialog";
|
||||
import { INPUT_LABELS, OUTPUT_LABELS } from "@/lib/luxsinApi";
|
||||
import { INPUT_LABELS, isHomeVolumeLockedByPassthrough, OUTPUT_LABELS } from "@/lib/luxsinApi";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
ChevronRight,
|
||||
@@ -378,6 +378,38 @@ export default function Home() {
|
||||
};
|
||||
}, []);
|
||||
|
||||
const volumePassthroughActive = isHomeVolumeLockedByPassthrough(
|
||||
deviceState?.dacVolumeDirect,
|
||||
deviceState?.output,
|
||||
);
|
||||
const volumePassthroughModeLabel =
|
||||
deviceState?.dacVolumeDirect === 1
|
||||
? (homeText.volumePassthroughMode0dB ?? "0dB")
|
||||
: deviceState?.dacVolumeDirect === 2
|
||||
? (homeText.volumePassthroughMode12dB ?? "-12dB")
|
||||
: "";
|
||||
|
||||
const notifyVolumePassthroughBlocked = useCallback(() => {
|
||||
const template =
|
||||
homeText.volumePassthroughBlocked ??
|
||||
"当前为音量直通模式({mode}),无法调节耳机音量。请在音频设置中关闭直通或改为「关闭」。";
|
||||
toast.info(template.replace("{mode}", volumePassthroughModeLabel));
|
||||
}, [homeText, volumePassthroughModeLabel]);
|
||||
|
||||
const blockVolumeAdjust = useCallback(() => {
|
||||
if (!volumePassthroughActive) return false;
|
||||
notifyVolumePassthroughBlocked();
|
||||
return true;
|
||||
}, [volumePassthroughActive, notifyVolumePassthroughBlocked]);
|
||||
|
||||
const applyVolume = useCallback(
|
||||
(v: number) => {
|
||||
if (blockVolumeAdjust()) return;
|
||||
setVolume(v);
|
||||
},
|
||||
[blockVolumeAdjust, setVolume],
|
||||
);
|
||||
|
||||
if (!isConnected) return <ConnectionPlaceholder />;
|
||||
|
||||
const ds = deviceState!;
|
||||
@@ -413,6 +445,7 @@ export default function Home() {
|
||||
};
|
||||
|
||||
const beginKnobDrag = (e: React.PointerEvent<HTMLDivElement>) => {
|
||||
if (blockVolumeAdjust()) return;
|
||||
const el = e.currentTarget;
|
||||
const rect = el.getBoundingClientRect();
|
||||
knobDraggingRef.current = true;
|
||||
@@ -442,7 +475,7 @@ export default function Home() {
|
||||
knobDraggingRef.current = false;
|
||||
knobActivePointerIdRef.current = null;
|
||||
isDragging.current = false;
|
||||
setVolume(knobLastVolRef.current);
|
||||
applyVolume(knobLastVolRef.current);
|
||||
try {
|
||||
e.currentTarget.releasePointerCapture(e.pointerId);
|
||||
} catch {
|
||||
@@ -549,7 +582,7 @@ export default function Home() {
|
||||
</div>
|
||||
</div>
|
||||
{/* Right knob */}
|
||||
<div className="pr-4">
|
||||
<div className={cn("pr-4", volumePassthroughActive && "opacity-40 pointer-events-none")}>
|
||||
<div
|
||||
className="w-12 h-12 rounded-full flex items-center justify-center touch-none select-none"
|
||||
style={{
|
||||
@@ -564,6 +597,7 @@ export default function Home() {
|
||||
aria-valuemin={0}
|
||||
aria-valuemax={200}
|
||||
aria-valuenow={localVol}
|
||||
aria-disabled={volumePassthroughActive}
|
||||
onPointerDown={beginKnobDrag}
|
||||
onPointerMove={moveKnobDrag}
|
||||
onPointerUp={endKnobDrag}
|
||||
@@ -583,12 +617,36 @@ export default function Home() {
|
||||
</div>
|
||||
|
||||
{/* ── Volume slider ── */}
|
||||
<div className="px-4 mb-5">
|
||||
<div className="flex items-center gap-3">
|
||||
<div
|
||||
className={cn("px-4 mb-5", volumePassthroughActive && "opacity-45")}
|
||||
onPointerDownCapture={(e) => {
|
||||
if (volumePassthroughActive) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
notifyVolumePassthroughBlocked();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{volumePassthroughActive && (
|
||||
<p className="text-center text-[13px] mb-3 px-2" style={{ color: "rgba(0,255,246,0.85)" }}>
|
||||
{homeText.volumePassthroughHint ?? "当前为音量直通模式,音量不可调节"}
|
||||
{volumePassthroughModeLabel ? ` (${volumePassthroughModeLabel})` : ""}
|
||||
</p>
|
||||
)}
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center gap-3",
|
||||
volumePassthroughActive && "pointer-events-none",
|
||||
)}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setVolumeConfirm("min")}
|
||||
className="text-white/50 active:text-white transition-colors flex-shrink-0"
|
||||
disabled={volumePassthroughActive}
|
||||
onClick={() => {
|
||||
if (blockVolumeAdjust()) return;
|
||||
setVolumeConfirm("min");
|
||||
}}
|
||||
className="text-white/50 active:text-white transition-colors flex-shrink-0 disabled:opacity-40"
|
||||
>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" className="w-6 h-6">
|
||||
<polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/>
|
||||
@@ -606,25 +664,28 @@ export default function Home() {
|
||||
}} />
|
||||
<input
|
||||
type="range" min={0} max={200} value={localVol}
|
||||
className="cyan-slider relative z-10"
|
||||
disabled={volumePassthroughActive}
|
||||
className="cyan-slider relative z-10 disabled:opacity-50"
|
||||
onChange={(e) => {
|
||||
if (volumePassthroughActive) return;
|
||||
const v = Number(e.target.value);
|
||||
setLocalVol(v);
|
||||
}}
|
||||
onPointerDown={() => {
|
||||
if (volumePassthroughActive) return;
|
||||
isDragging.current = true;
|
||||
}}
|
||||
onPointerUp={(e) => {
|
||||
isDragging.current = false;
|
||||
setVolume(Number(e.currentTarget.value));
|
||||
applyVolume(Number(e.currentTarget.value));
|
||||
}}
|
||||
onPointerCancel={(e) => {
|
||||
isDragging.current = false;
|
||||
setVolume(Number(e.currentTarget.value));
|
||||
applyVolume(Number(e.currentTarget.value));
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
isDragging.current = false;
|
||||
setVolume(Number(e.currentTarget.value));
|
||||
applyVolume(Number(e.currentTarget.value));
|
||||
}}
|
||||
onKeyUp={(e) => {
|
||||
const k = e.key;
|
||||
@@ -638,7 +699,7 @@ export default function Home() {
|
||||
k === "PageUp" ||
|
||||
k === "PageDown"
|
||||
) {
|
||||
setVolume(Number(e.currentTarget.value));
|
||||
applyVolume(Number(e.currentTarget.value));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
@@ -646,8 +707,12 @@ export default function Home() {
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setVolumeConfirm("max")}
|
||||
className="text-white/50 active:text-white transition-colors flex-shrink-0"
|
||||
disabled={volumePassthroughActive}
|
||||
onClick={() => {
|
||||
if (blockVolumeAdjust()) return;
|
||||
setVolumeConfirm("max");
|
||||
}}
|
||||
className="text-white/50 active:text-white transition-colors flex-shrink-0 disabled:opacity-40"
|
||||
>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" className="w-6 h-6">
|
||||
<polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/>
|
||||
@@ -657,11 +722,16 @@ export default function Home() {
|
||||
</button>
|
||||
</div>
|
||||
{/* dB value */}
|
||||
<div className="mt-[6px] flex items-center justify-center gap-3">
|
||||
<div
|
||||
className={cn(
|
||||
"mt-[6px] flex items-center justify-center gap-3",
|
||||
volumePassthroughActive && "pointer-events-none",
|
||||
)}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Decrease volume"
|
||||
disabled={localVol <= 0}
|
||||
disabled={volumePassthroughActive || localVol <= 0}
|
||||
className="w-10 h-10 rounded-full flex items-center justify-center transition-all active:scale-95 disabled:opacity-30 disabled:cursor-not-allowed"
|
||||
style={{
|
||||
background: "rgba(44,44,46,0.6)",
|
||||
@@ -669,22 +739,29 @@ export default function Home() {
|
||||
color: "rgba(255,255,255,0.55)",
|
||||
}}
|
||||
onClick={() => {
|
||||
if (blockVolumeAdjust()) return;
|
||||
const next = Math.max(0, Math.min(200, localVol - fineVolumeStep));
|
||||
setLocalVol(next);
|
||||
setVolume(next);
|
||||
applyVolume(next);
|
||||
}}
|
||||
>
|
||||
<span className="text-[20px] leading-none">-</span>
|
||||
</button>
|
||||
|
||||
<span className="text-[22px] font-bold tracking-tight" style={{ color: "#00FFF6" }}>
|
||||
<span
|
||||
className={cn(
|
||||
"text-[22px] font-bold tracking-tight",
|
||||
volumePassthroughActive ? "text-white/35" : "",
|
||||
)}
|
||||
style={volumePassthroughActive ? undefined : { color: "#00FFF6" }}
|
||||
>
|
||||
{volToDB(localVol)}
|
||||
</span>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Increase volume"
|
||||
disabled={localVol >= 200}
|
||||
disabled={volumePassthroughActive || localVol >= 200}
|
||||
className="w-10 h-10 rounded-full flex items-center justify-center transition-all active:scale-95 disabled:opacity-30 disabled:cursor-not-allowed"
|
||||
style={{
|
||||
background: "rgba(44,44,46,0.6)",
|
||||
@@ -692,9 +769,10 @@ export default function Home() {
|
||||
color: "rgba(255,255,255,0.55)",
|
||||
}}
|
||||
onClick={() => {
|
||||
if (blockVolumeAdjust()) return;
|
||||
const next = Math.max(0, Math.min(200, localVol + fineVolumeStep));
|
||||
setLocalVol(next);
|
||||
setVolume(next);
|
||||
applyVolume(next);
|
||||
}}
|
||||
>
|
||||
<span className="text-[20px] leading-none">+</span>
|
||||
@@ -877,10 +955,10 @@ export default function Home() {
|
||||
onClick={() => {
|
||||
if (volumeConfirm === "min") {
|
||||
setLocalVol(0);
|
||||
setVolume(0);
|
||||
applyVolume(0);
|
||||
} else if (volumeConfirm === "max") {
|
||||
setLocalVol(200);
|
||||
setVolume(200);
|
||||
applyVolume(200);
|
||||
}
|
||||
setVolumeConfirm(null);
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user