适配b9
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
|
||||
<title>Luxsin X9 Controller</title>
|
||||
<title>Luxsin B9 Controller</title>
|
||||
<style>
|
||||
body { margin:0; display:flex; align-items:center; justify-content:center; min-height:100vh; background:#000; color:rgba(255,255,255,.5); font-family:-apple-system,sans-serif; }
|
||||
.spinner { width:24px; height:24px; border:3px solid rgba(255,255,255,.15); border-top-color:#00FFF6; border-radius:50%; animation:spin .8s linear infinite; margin-right:12px; }
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
|
||||
<title>Luxsin X9 Controller (Legacy)</title>
|
||||
<title>Luxsin B9 Controller (Legacy)</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
|
||||
<title>Luxsin X9 Controller</title>
|
||||
<title>Luxsin B9 Controller</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
|
Before Width: | Height: | Size: 195 KiB After Width: | Height: | Size: 208 KiB |
|
Before Width: | Height: | Size: 166 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 386 KiB |
|
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 101 KiB |
|
Before Width: | Height: | Size: 141 KiB After Width: | Height: | Size: 90 KiB |
|
After Width: | Height: | Size: 141 KiB |
|
Before Width: | Height: | Size: 208 KiB After Width: | Height: | Size: 195 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 166 KiB |
|
Before Width: | Height: | Size: 386 KiB After Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 90 KiB |
@@ -11,7 +11,6 @@ import Home from "./pages/Home";
|
||||
import NotFound from "./pages/NotFound";
|
||||
import Dashboard from "./pages/Dashboard";
|
||||
import AudioPage from "./pages/AudioPage";
|
||||
import BluetoothPage from "./pages/BluetoothPage";
|
||||
import SystemPage from "./pages/SystemPage";
|
||||
import IOPage from "./pages/IOPage";
|
||||
import VUPage from "./pages/VUPage";
|
||||
@@ -61,7 +60,6 @@ function AppRouter() {
|
||||
<Route path="/audio" component={AudioPage} />
|
||||
<Route path="/eq" component={EQPageRoute} />
|
||||
<Route path="/effects" component={EffectsPageRoute} />
|
||||
<Route path="/bluetooth" component={BluetoothPage} />
|
||||
<Route path="/system" component={SystemPage} />
|
||||
<Route path="/io" component={IOPage} />
|
||||
<Route path="/vu" component={VUPage} />
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
import { useDevice } from "@/contexts/DeviceContext";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
Bluetooth,
|
||||
ChevronRight,
|
||||
Gauge,
|
||||
Layers,
|
||||
@@ -27,7 +26,6 @@ const NAV_ITEMS: NavItem[] = [
|
||||
{ path: "/audio", icon: Volume2, label: "Audio" },
|
||||
{ path: "/eq", icon: Sliders, label: "Parametric EQ" },
|
||||
{ path: "/effects", icon: Layers, label: "Effects" },
|
||||
{ path: "/bluetooth", icon: Bluetooth, label: "Bluetooth" },
|
||||
{ path: "/system", icon: Settings, label: "System" },
|
||||
];
|
||||
|
||||
|
||||
@@ -257,7 +257,7 @@ export interface PeqSoftPayload {
|
||||
// ============================================================
|
||||
// Input/Output Labels
|
||||
// ============================================================
|
||||
export const INPUT_LABELS = ["USB", "USB-C", "Coaxial", "Optical", "Bluetooth", "HDMI-EARC", "RCA"];
|
||||
export const INPUT_LABELS = ["USB", "USB-C", "Coaxial", "Optical", "Bluetooth", "IIS", "RCA"];
|
||||
export const OUTPUT_LABELS = ["XLR", "RCA", "Headset", "XLR/RCA"];
|
||||
|
||||
/** Device `input` index for Bluetooth source. */
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
"pageTitle": "I/O",
|
||||
"inputSection": "Eingang",
|
||||
"outputSection": "Ausgang",
|
||||
"inputOptions": ["USB-B", "USB-C", "Koaxial", "Optisch", "Bluetooth", "HDMI-EARC", "RCA", "USB Driver"],
|
||||
"inputOptions": ["USB-B", "USB-C", "Koaxial", "Optisch", "Bluetooth", "IIS", "RCA", "USB Driver"],
|
||||
"outputOptions": ["XLR", "RCA", "XLR/RCA", "Kopfhörer"],
|
||||
"input": {
|
||||
"coaxial": "KOAXIAL",
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
"pageTitle": "I/O",
|
||||
"inputSection": "Input",
|
||||
"outputSection": "Output",
|
||||
"inputOptions": ["USB-B", "USB-C", "Coaxial", "Optical", "Bluetooth", "HDMI-EARC", "RCA", "USB Driver"],
|
||||
"inputOptions": ["USB-B", "USB-C", "Coaxial", "Optical", "Bluetooth", "IIS", "RCA", "USB Driver"],
|
||||
"outputOptions": ["XLR", "RCA", "XLR/RCA", "Headset"],
|
||||
"input": {
|
||||
"coaxial": "COAXIAL",
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
"pageTitle": "E/S",
|
||||
"inputSection": "Entrada",
|
||||
"outputSection": "Salida",
|
||||
"inputOptions": ["USB-B", "USB-C", "Coaxial", "Óptico", "Bluetooth", "HDMI-EARC", "RCA", "USB Driver"],
|
||||
"inputOptions": ["USB-B", "USB-C", "Coaxial", "Óptico", "Bluetooth", "IIS", "RCA", "USB Driver"],
|
||||
"outputOptions": ["XLR", "RCA", "XLR/RCA", "Auriculares"],
|
||||
"input": {
|
||||
"coaxial": "COAXIAL",
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
"pageTitle": "E/S",
|
||||
"inputSection": "Entrée",
|
||||
"outputSection": "Sortie",
|
||||
"inputOptions": ["USB-B", "USB-C", "Coaxial", "Optique", "Bluetooth", "HDMI-EARC", "RCA", "USB Driver"],
|
||||
"inputOptions": ["USB-B", "USB-C", "Coaxial", "Optique", "Bluetooth", "IIS", "RCA", "USB Driver"],
|
||||
"outputOptions": ["XLR", "RCA", "XLR/RCA", "Casque"],
|
||||
"input": {
|
||||
"coaxial": "COAXIAL",
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
"pageTitle": "輸入/輸出",
|
||||
"inputSection": "輸入",
|
||||
"outputSection": "輸出",
|
||||
"inputOptions": ["USB-B", "USB-C", "同軸", "光纖", "藍牙", "HDMI-EARC", "類比RCA", "USB隨身碟"],
|
||||
"inputOptions": ["USB-B", "USB-C", "同軸", "光纖", "藍牙", "IIS", "類比RCA", "USB隨身碟"],
|
||||
"outputOptions": ["XLR", "RCA", "XLR/RCA", "耳機"],
|
||||
"input": { "coaxial": "同軸", "optical": "光纖", "bluetooth": "藍牙", "rca": "類比RCA" },
|
||||
"output": { "headset": "耳機" }
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
"pageTitle": "输入输出",
|
||||
"inputSection": "输入",
|
||||
"outputSection": "输出",
|
||||
"inputOptions": ["USB-B", "USB-C", "同轴", "光纤", "蓝牙", "HDMI-EARC", "模拟RCA", "U盘"],
|
||||
"inputOptions": ["USB-B", "USB-C", "同轴", "光纤", "蓝牙", "IIS", "模拟RCA", "U盘"],
|
||||
"outputOptions": ["XLR", "RCA", "XLR/RCA", "耳机"],
|
||||
"input": { "coaxial": "同轴", "optical": "光纤", "bluetooth": "蓝牙", "rca": "模拟RCA" },
|
||||
"output": { "headset": "耳机" }
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
Sections:
|
||||
- 平衡与灵敏度: 左右平衡, VU灵敏度, 模拟输入增益 sliders
|
||||
- 输出特性: 滤波特性, 耳机增益, 音量幅度
|
||||
- IIS设置: IIS静音电平, IIS模式
|
||||
- 系统与接口: 开机音量, XLR极性
|
||||
All list rows → navigate to /select page
|
||||
============================================================ */
|
||||
@@ -107,6 +108,7 @@ type LocaleDac = {
|
||||
dac?: {
|
||||
sectionBalanceSensitivity?: string;
|
||||
sectionOutput?: string;
|
||||
sectionIisSettings?: string;
|
||||
sectionSystemInterface?: string;
|
||||
balance?: string;
|
||||
sensitivity?: string;
|
||||
@@ -119,6 +121,8 @@ type LocaleDac = {
|
||||
volumeStep?: string;
|
||||
bootVolume?: string | { label?: string; options?: Array<{ index: number; label: string }> };
|
||||
xlr?: { label?: string; options?: Array<{ index: number; label: string }> };
|
||||
mutePolar?: { label?: string; options?: Array<{ index: number; label: string }> };
|
||||
IISMode?: { label?: string; options?: Array<{ index: number; label: string }> };
|
||||
};
|
||||
home?: { audioSet?: string };
|
||||
};
|
||||
@@ -222,6 +226,8 @@ export default function AudioPage() {
|
||||
const stepIdx = ds?.soundStep ?? 1;
|
||||
const filterIdx = ds?.pcm ?? 0;
|
||||
const xlrIdx = ds?.xlr ?? 0;
|
||||
const mutePolarIdx = (ds as ({ hdmimutepolar?: number } | null))?.hdmimutepolar ?? 0;
|
||||
const iisModeIdx = (ds as ({ hdmiType?: number } | null))?.hdmiType ?? 0;
|
||||
const bootSoundVal = normalizeBootSound(localBootSound);
|
||||
const firmwareVersion = parseFirmwareVersion(ds?.version);
|
||||
const bootSoundMaxIdx = getBootSoundMaxIndex(firmwareVersion);
|
||||
@@ -233,6 +239,8 @@ export default function AudioPage() {
|
||||
const DAC_VOLUME_DIRECT_OPTIONS = optionLabels(dacText.dacVolumeDirect?.options, ["关闭", "0dB", "-12dB"]);
|
||||
const DAC_ARC_OPTIONS = optionLabels(dacText.dacArc?.options, ["ARC", "EARC"]);
|
||||
const XLR_OPTIONS = optionLabels(dacText.xlr?.options, ["正常", "反转"]);
|
||||
const MUTE_POLAR_OPTIONS = optionLabels(dacText.mutePolar?.options, ["低电平", "高电平"]);
|
||||
const IIS_MODE_OPTIONS = optionLabels(dacText.IISMode?.options, ["模式1", "模式2", "模式3", "模式4", "模式5", "模式6", "模式7", "模式8"]);
|
||||
|
||||
const pageTitle = usePageTitleWithConnection(localeData.home?.audioSet ?? "Audio");
|
||||
|
||||
@@ -380,6 +388,21 @@ export default function AudioPage() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* ── IIS 设置 ── */}
|
||||
<SectionHeader title={dacText.sectionIisSettings ?? "IIS设置"} />
|
||||
<div className="ios-list-group">
|
||||
<ListRow
|
||||
label={dacText.mutePolar?.label ?? "IIS 静音电平"}
|
||||
value={MUTE_POLAR_OPTIONS[mutePolarIdx] ?? MUTE_POLAR_OPTIONS[0] ?? "—"}
|
||||
onClick={() => goSelect(dacText.mutePolar?.label ?? "选择 IIS 静音电平", MUTE_POLAR_OPTIONS, mutePolarIdx, "mutePolar")}
|
||||
/>
|
||||
<ListRow
|
||||
label={dacText.IISMode?.label ?? "IIS 模式"}
|
||||
value={IIS_MODE_OPTIONS[iisModeIdx] ?? IIS_MODE_OPTIONS[0] ?? "—"}
|
||||
onClick={() => goSelect(dacText.IISMode?.label ?? "选择 IIS 模式", IIS_MODE_OPTIONS, iisModeIdx, "IISMode")}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* ── 系统与接口 ── */}
|
||||
<SectionHeader title={dacText.sectionSystemInterface ?? "系统与接口"} />
|
||||
<div className="ios-list-group">
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
/* ============================================================
|
||||
BLUETOOTH PAGE — BT status, transport controls
|
||||
============================================================ */
|
||||
import Layout from "@/components/Layout";
|
||||
import { useDevice } from "@/contexts/DeviceContext";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { BT_STATUS_LABELS } from "@/lib/luxsinApi";
|
||||
import { Bluetooth, Music, SkipBack, SkipForward, Volume2 } from "lucide-react";
|
||||
|
||||
export default function BluetoothPage() {
|
||||
const { deviceState, updateSetting } = useDevice();
|
||||
|
||||
if (!deviceState) {
|
||||
return (
|
||||
<Layout title="Bluetooth" subtitle="Bluetooth audio source">
|
||||
<div className="glass-card p-8 text-center text-white/30">Connect to device</div>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
const { bt_status, bt_title, bt_artist, bt_srcname } = deviceState;
|
||||
const isPlaying = bt_status === 1;
|
||||
const isConnected = bt_status > 0;
|
||||
|
||||
return (
|
||||
<Layout title="Bluetooth" subtitle="Bluetooth audio source">
|
||||
<div className="max-w-2xl mx-auto space-y-4">
|
||||
|
||||
{/* Status card */}
|
||||
<div className={cn(
|
||||
"glass-card p-6 animate-fade-in-up transition-all duration-300",
|
||||
isConnected && "glass-cyan"
|
||||
)}>
|
||||
<div className="flex items-center gap-4 mb-6">
|
||||
<div className={cn(
|
||||
"w-14 h-14 rounded-2xl flex items-center justify-center",
|
||||
isConnected ? "glass-cyan" : "bg-white/5"
|
||||
)}>
|
||||
<Bluetooth size={24} className={isConnected ? "text-[#00FFF6]" : "text-white/30"} />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className={cn(
|
||||
"text-lg font-bold",
|
||||
isConnected ? "text-[#00FFF6]" : "text-white/50"
|
||||
)} style={{ fontFamily: "'Space Grotesk', sans-serif" }}>
|
||||
{isConnected ? bt_srcname || "Connected" : "Not Connected"}
|
||||
</h2>
|
||||
<p className="text-white/40 text-sm">{BT_STATUS_LABELS[bt_status]}</p>
|
||||
</div>
|
||||
{isConnected && <div className="ml-auto dot-active" />}
|
||||
</div>
|
||||
|
||||
{isConnected ? (
|
||||
<>
|
||||
{/* Track info */}
|
||||
<div className="glass-card p-4 mb-5 rounded-[16px]">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 rounded-[10px] bg-white/5 flex items-center justify-center flex-shrink-0">
|
||||
<Music size={16} className="text-white/30" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-white font-semibold text-sm truncate"
|
||||
style={{ fontFamily: "'Space Grotesk', sans-serif" }}>
|
||||
{bt_title || "Unknown Track"}
|
||||
</p>
|
||||
<p className="text-white/50 text-xs truncate mt-0.5">
|
||||
{bt_artist || "Unknown Artist"}
|
||||
</p>
|
||||
</div>
|
||||
{isPlaying && (
|
||||
<div className="flex items-end gap-0.5 h-5 flex-shrink-0">
|
||||
{[1, 2, 3, 4].map(i => (
|
||||
<div
|
||||
key={i}
|
||||
className="w-1 rounded-sm bg-[#00FFF6]"
|
||||
style={{
|
||||
height: `${30 + Math.random() * 70}%`,
|
||||
animation: `vu-pulse ${0.3 + i * 0.1}s ease-in-out infinite alternate`,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Transport controls */}
|
||||
<div className="flex items-center justify-center gap-6">
|
||||
<button
|
||||
onClick={() => updateSetting({ bt_next: 0 })}
|
||||
className="btn-glass w-12 h-12 flex items-center justify-center"
|
||||
>
|
||||
<SkipBack size={18} className="text-white/60" />
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => updateSetting({ bt_play: 1 })}
|
||||
className={cn(
|
||||
"w-16 h-16 rounded-full flex items-center justify-center transition-all duration-200",
|
||||
isPlaying ? "btn-cyan" : "btn-glass"
|
||||
)}
|
||||
style={isPlaying ? {
|
||||
boxShadow: "0 0 30px rgba(0,255,246,0.4)"
|
||||
} : {}}
|
||||
>
|
||||
{isPlaying ? (
|
||||
<div className="flex gap-1">
|
||||
<div className="w-1.5 h-5 bg-current rounded-sm" />
|
||||
<div className="w-1.5 h-5 bg-current rounded-sm" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-0 h-0 border-l-[14px] border-l-current border-y-[9px] border-y-transparent ml-1" />
|
||||
)}
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => updateSetting({ bt_next: 1 })}
|
||||
className="btn-glass w-12 h-12 flex items-center justify-center"
|
||||
>
|
||||
<SkipForward size={18} className="text-white/60" />
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="flex flex-col items-center py-8 text-white/30">
|
||||
<Bluetooth size={40} className="mb-3 opacity-20" />
|
||||
<p className="text-sm">No Bluetooth device connected</p>
|
||||
<p className="text-xs mt-1 opacity-60">Switch input to Bluetooth to use this feature</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Volume info */}
|
||||
{isConnected && (
|
||||
<div className="glass-card p-4 flex items-center gap-3 animate-fade-in-up" style={{ animationDelay: "60ms" }}>
|
||||
<Volume2 size={14} className="text-[#00FFF6]" />
|
||||
<span className="text-white/60 text-sm flex-1">Current Volume</span>
|
||||
<span className="text-[#00FFF6] font-bold text-lg"
|
||||
style={{ fontFamily: "'JetBrains Mono', monospace" }}>
|
||||
{deviceState.volume}
|
||||
</span>
|
||||
<span className="text-white/30 text-xs">/ 200</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
@@ -36,7 +36,6 @@ import {
|
||||
Sliders,
|
||||
Activity,
|
||||
Gauge,
|
||||
Undo2,
|
||||
Bluetooth,
|
||||
SkipBack,
|
||||
SkipForward,
|
||||
@@ -635,7 +634,7 @@ export default function Home() {
|
||||
</div>
|
||||
{/* Brand label */}
|
||||
<div className="absolute top-5 left-8">
|
||||
<span className="text-[11px] font-bold tracking-widest text-white/30">LUXSIN</span>
|
||||
<span className="text-[11px] font-bold tracking-widest text-white/30">{deviceState?.device ?? "LUXSIN"}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -965,15 +964,6 @@ export default function Home() {
|
||||
thumbnail={`${import.meta.env.BASE_URL}images/vu/vu${(ds.vu ?? 0) + 1}.png`}
|
||||
onClick={() => setLocation("/vu")}
|
||||
/>
|
||||
<ListRow
|
||||
icon={<Undo2 size={18} />}
|
||||
label={homeText.backToLegacy ?? "返回旧版"}
|
||||
onClick={() => {
|
||||
const target = ip.trim();
|
||||
if (!target) return;
|
||||
window.location.href = `https://am.luxsinaudio.com/i.html?ip=${encodeURIComponent(target)}`;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -164,7 +164,7 @@ const OUTPUT_OPTION_DEFS: IOOptionDef[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const FALLBACK_INPUT_LABELS = ["USB-B", "USB-C", "同轴", "光纤", "蓝牙", "HDMI-EARC", "RCA", "USB Driver"];
|
||||
const FALLBACK_INPUT_LABELS = ["USB-B", "USB-C", "同轴", "光纤", "蓝牙", "IIS", "RCA", "USB Driver"];
|
||||
const FALLBACK_OUTPUT_LABELS = ["XLR", "RCA", "XLR/RCA", "耳机"];
|
||||
|
||||
function withLabels(defs: IOOptionDef[], labels: string[] | undefined, fallback: string[]): IOOption[] {
|
||||
|
||||