Implement Luxsin audio API integration: Add proxy configuration in Vite for CORS handling, enhance luxsinApi with brand and model fetching functions, and update EQPage to support audio catalog browsing and searching. Introduce new state management for catalog brands and models.
This commit is contained in:
@@ -143,6 +143,10 @@ export interface PeqChangePayload {
|
||||
autoPre?: number;
|
||||
preamp?: number;
|
||||
canDel?: number;
|
||||
brand?: string;
|
||||
model?: string;
|
||||
target?: string;
|
||||
form?: string;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -330,3 +334,153 @@ export const MOCK_PEQ_STATE: PeqState = {
|
||||
{ fc: 12000, gain: 1.5, q: 0.7, type: 6 },
|
||||
],
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// Luxsin cloud audio catalog (brands / models)
|
||||
// Dev: proxied via Vite (`/luxsin-audio-api` → `/audio`) to avoid Origin-based 403.
|
||||
// ============================================================
|
||||
|
||||
export interface LuxsinAudioBrand {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface LuxsinAudioModel {
|
||||
id: number;
|
||||
name: string;
|
||||
form?: string;
|
||||
}
|
||||
|
||||
export interface LuxsinAudioModelListItem {
|
||||
brandName: string;
|
||||
modelName: string;
|
||||
form?: string;
|
||||
source?: string;
|
||||
}
|
||||
|
||||
export function buildLuxsinAudioUrl(resourcePath: string): string {
|
||||
const path = resourcePath.replace(/^\//, "");
|
||||
if (import.meta.env.DEV) {
|
||||
return `/luxsin-audio-api/${path}`;
|
||||
}
|
||||
return `https://api.luxsin.com.cn/audio/${path}`;
|
||||
}
|
||||
|
||||
/** Fetches `getBrand`; body is custom Base64 text, decodes to JSON array of `{ id, name }`. */
|
||||
export async function fetchLuxsinAudioBrands(): Promise<LuxsinAudioBrand[]> {
|
||||
const res = await fetch(buildLuxsinAudioUrl("getBrand"));
|
||||
if (!res.ok) {
|
||||
throw new Error(`getBrand HTTP ${res.status}`);
|
||||
}
|
||||
const body = (await res.text()).trim();
|
||||
const json = decodeCustomBase64(body);
|
||||
const data = JSON.parse(json) as unknown;
|
||||
if (!Array.isArray(data)) {
|
||||
throw new Error("getBrand: response is not an array");
|
||||
}
|
||||
return data
|
||||
.map((row) => {
|
||||
if (!row || typeof row !== "object") return null;
|
||||
const r = row as { id?: unknown; name?: unknown };
|
||||
const name = typeof r.name === "string" ? r.name : "";
|
||||
if (!name) return null;
|
||||
const id = typeof r.id === "number" ? r.id : Number(r.id);
|
||||
return { id: Number.isFinite(id) ? id : 0, name };
|
||||
})
|
||||
.filter((b): b is LuxsinAudioBrand => b !== null);
|
||||
}
|
||||
|
||||
/** Fetches `getModel?brandName=...`; body is custom Base64 text, decodes to JSON array of model rows. */
|
||||
export async function fetchLuxsinAudioModels(brandName: string): Promise<LuxsinAudioModel[]> {
|
||||
const brand = brandName.trim();
|
||||
if (!brand) return [];
|
||||
|
||||
const res = await fetch(buildLuxsinAudioUrl(`getModel?brandName=${encodeURIComponent(brand)}`));
|
||||
if (!res.ok) {
|
||||
throw new Error(`getModel HTTP ${res.status}`);
|
||||
}
|
||||
const body = (await res.text()).trim();
|
||||
const json = decodeCustomBase64(body);
|
||||
const data = JSON.parse(json) as unknown;
|
||||
if (!Array.isArray(data)) {
|
||||
throw new Error("getModel: response is not an array");
|
||||
}
|
||||
return data
|
||||
.map((row) => {
|
||||
if (!row || typeof row !== "object") return null;
|
||||
const r = row as { id?: unknown; name?: unknown; form?: unknown };
|
||||
const name = typeof r.name === "string" ? r.name : "";
|
||||
if (!name) return null;
|
||||
const id = typeof r.id === "number" ? r.id : Number(r.id);
|
||||
const form = typeof r.form === "string" ? r.form : undefined;
|
||||
return {
|
||||
id: Number.isFinite(id) ? id : 0,
|
||||
name,
|
||||
...(form ? { form } : {}),
|
||||
};
|
||||
})
|
||||
.filter((m): m is LuxsinAudioModel => m !== null);
|
||||
}
|
||||
|
||||
/** Fetches `modelList?key=...&count=...`; body is custom Base64 text, decodes to brand+model rows. */
|
||||
export async function fetchLuxsinAudioModelList(
|
||||
key: string,
|
||||
count = 1000,
|
||||
): Promise<LuxsinAudioModelListItem[]> {
|
||||
const q = key.trim();
|
||||
if (!q) return [];
|
||||
const safeCount = Number.isFinite(count) ? Math.max(1, Math.floor(count)) : 1000;
|
||||
const res = await fetch(
|
||||
buildLuxsinAudioUrl(`modelList?key=${encodeURIComponent(q)}&count=${safeCount}`),
|
||||
);
|
||||
if (!res.ok) {
|
||||
throw new Error(`modelList HTTP ${res.status}`);
|
||||
}
|
||||
const body = (await res.text()).trim();
|
||||
const json = decodeCustomBase64(body);
|
||||
const data = JSON.parse(json) as unknown;
|
||||
if (!Array.isArray(data)) {
|
||||
throw new Error("modelList: response is not an array");
|
||||
}
|
||||
return data
|
||||
.map((row): LuxsinAudioModelListItem | null => {
|
||||
if (!row || typeof row !== "object") return null;
|
||||
const r = row as {
|
||||
brand_name?: unknown;
|
||||
name?: unknown;
|
||||
form?: unknown;
|
||||
source?: unknown;
|
||||
};
|
||||
const brandName = typeof r.brand_name === "string" ? r.brand_name : "";
|
||||
const modelName = typeof r.name === "string" ? r.name : "";
|
||||
if (!brandName || !modelName) return null;
|
||||
const form = typeof r.form === "string" ? r.form : undefined;
|
||||
const source = typeof r.source === "string" ? r.source : undefined;
|
||||
return {
|
||||
brandName,
|
||||
modelName,
|
||||
...(form ? { form } : {}),
|
||||
...(source ? { source } : {}),
|
||||
};
|
||||
})
|
||||
.filter((item): item is LuxsinAudioModelListItem => item !== null);
|
||||
}
|
||||
|
||||
/** Fetches `getCurve` and returns decoded text payload. */
|
||||
export async function fetchLuxsinAudioCurve(
|
||||
brand: string,
|
||||
name: string,
|
||||
target: string,
|
||||
): Promise<string> {
|
||||
const brandName = encodeURIComponent(brand.trim());
|
||||
const modelName = encodeURIComponent(name.trim());
|
||||
const targetName = encodeURIComponent(target.trim());
|
||||
const res = await fetch(
|
||||
buildLuxsinAudioUrl(`getCurve?brand=${brandName}&name=${modelName}&target=${targetName}`),
|
||||
);
|
||||
if (!res.ok) {
|
||||
throw new Error(`getCurve HTTP ${res.status}`);
|
||||
}
|
||||
const body = (await res.text()).trim();
|
||||
return decodeCustomBase64(body);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user