196 lines
6.2 KiB
JavaScript
196 lines
6.2 KiB
JavaScript
const connection = document.querySelector("#connection");
|
|
const detectionsEl = document.querySelector("#detections");
|
|
const frameIdEl = document.querySelector("#frame-id");
|
|
const fpsEl = document.querySelector("#fps");
|
|
const errorEl = document.querySelector("#error");
|
|
const sourceEl = document.querySelector("#source");
|
|
const deviceSelect = document.querySelector("#device-select");
|
|
const timingTokenEl = document.querySelector("#timing-token");
|
|
const timingSignEl = document.querySelector("#timing-sign");
|
|
const timingUrlEl = document.querySelector("#timing-url");
|
|
const timingOpenEl = document.querySelector("#timing-open");
|
|
const timingFrameEl = document.querySelector("#timing-frame");
|
|
|
|
let selectedDevice = "";
|
|
let pendingDevice = "";
|
|
let queuedDevice = "";
|
|
let switching = false;
|
|
let devicesSignature = "";
|
|
let lastWsSignature = "";
|
|
|
|
function setConnection(online, text) {
|
|
connection.textContent = text;
|
|
connection.classList.toggle("online", online);
|
|
connection.classList.toggle("offline", !online);
|
|
}
|
|
|
|
function formatMs(value) {
|
|
if (value === undefined || value === null || value === 0) {
|
|
return "-";
|
|
}
|
|
return `${Number(value).toFixed(2)} ms`;
|
|
}
|
|
|
|
function renderDevices(devices, currentDeviceNum) {
|
|
if (!devices.length) {
|
|
deviceSelect.innerHTML = '<option value="">未配置摄像头</option>';
|
|
deviceSelect.disabled = true;
|
|
return;
|
|
}
|
|
|
|
const displayDevice = pendingDevice || currentDeviceNum;
|
|
const nextSignature = `${displayDevice}|${devices.map((device) => device.device_num).join(",")}`;
|
|
if (nextSignature === devicesSignature) {
|
|
return;
|
|
}
|
|
|
|
selectedDevice = displayDevice;
|
|
devicesSignature = nextSignature;
|
|
deviceSelect.innerHTML = devices
|
|
.map((device) => {
|
|
const selected = device.device_num === displayDevice ? "selected" : "";
|
|
return `<option value="${device.device_num}" ${selected}>${device.name} · ${device.device_num}</option>`;
|
|
})
|
|
.join("");
|
|
}
|
|
|
|
function renderTimings(timings) {
|
|
timingTokenEl.textContent = timings?.token_cache ? "缓存" : formatMs(timings?.token_ms);
|
|
timingSignEl.textContent = formatMs(timings?.sign_ms);
|
|
timingUrlEl.textContent = formatMs(timings?.stream_url_ms);
|
|
timingOpenEl.textContent = formatMs(timings?.open_ms);
|
|
timingFrameEl.textContent = formatMs(timings?.first_frame_ms);
|
|
}
|
|
|
|
function renderDetections(detections) {
|
|
if (!detections.length) {
|
|
detectionsEl.className = "detections empty";
|
|
detectionsEl.textContent = "暂无目标";
|
|
return;
|
|
}
|
|
|
|
detectionsEl.className = "detections";
|
|
detectionsEl.innerHTML = detections
|
|
.map((det) => {
|
|
const score = `${(det.score * 100).toFixed(1)}%`;
|
|
const box = det.box.join(", ");
|
|
return `
|
|
<div class="det-item">
|
|
<div class="det-title">
|
|
<span>${det.label}</span>
|
|
<span>${score}</span>
|
|
</div>
|
|
<div class="det-box">box: [${box}]</div>
|
|
</div>
|
|
`;
|
|
})
|
|
.join("");
|
|
}
|
|
|
|
async function performSwitch(deviceNum) {
|
|
switching = true;
|
|
pendingDevice = deviceNum;
|
|
devicesSignature = "";
|
|
setConnection(false, "切换中");
|
|
console.log("[device-switch] start", { deviceNum });
|
|
|
|
try {
|
|
const response = await fetch(`/devices/${encodeURIComponent(deviceNum)}`, { method: "POST" });
|
|
if (!response.ok) {
|
|
throw new Error("切换摄像头失败");
|
|
}
|
|
const result = await response.json();
|
|
const video = document.querySelector("#video");
|
|
if (video) {
|
|
video.src = `/video?t=${Date.now()}`;
|
|
}
|
|
document.querySelectorAll(".grid-video").forEach((item) => {
|
|
item.src = `${item.dataset.src}?t=${Date.now()}`;
|
|
});
|
|
console.log("[device-switch] requested", { deviceNum, version: result.version });
|
|
} catch (error) {
|
|
pendingDevice = "";
|
|
devicesSignature = "";
|
|
setConnection(false, "切换失败");
|
|
console.error("[device-switch] failed", { deviceNum, error });
|
|
} finally {
|
|
switching = false;
|
|
if (queuedDevice && queuedDevice !== deviceNum) {
|
|
const nextDevice = queuedDevice;
|
|
queuedDevice = "";
|
|
return performSwitch(nextDevice);
|
|
}
|
|
queuedDevice = "";
|
|
}
|
|
}
|
|
|
|
function switchDevice(deviceNum) {
|
|
if (switching) {
|
|
queuedDevice = deviceNum;
|
|
pendingDevice = deviceNum;
|
|
devicesSignature = "";
|
|
setConnection(false, "等待切换");
|
|
console.log("[device-switch] queued", { deviceNum });
|
|
return Promise.resolve();
|
|
}
|
|
|
|
return performSwitch(deviceNum);
|
|
}
|
|
|
|
function connectWebSocket() {
|
|
const protocol = window.location.protocol === "https:" ? "wss" : "ws";
|
|
const ws = new WebSocket(`${protocol}://${window.location.host}/ws/detections`);
|
|
|
|
ws.addEventListener("open", () => setConnection(true, "已连接"));
|
|
|
|
ws.addEventListener("message", (event) => {
|
|
const data = JSON.parse(event.data);
|
|
frameIdEl.textContent = data.frame_id ?? "-";
|
|
fpsEl.textContent = data.fps ?? "-";
|
|
errorEl.textContent = data.error || (data.connected ? "正常" : "未连接");
|
|
sourceEl.textContent = data.source || "-";
|
|
setConnection(Boolean(data.connected), data.connected ? "已连接" : "重连中");
|
|
const wsSignature = `${data.current_device_num}|${data.connected}|${data.frame_id}|${data.error || ""}`;
|
|
if (wsSignature !== lastWsSignature) {
|
|
lastWsSignature = wsSignature;
|
|
console.log("[device-switch] ws", {
|
|
currentDeviceNum: data.current_device_num,
|
|
pendingDevice,
|
|
queuedDevice,
|
|
connected: data.connected,
|
|
frameId: data.frame_id,
|
|
error: data.error,
|
|
});
|
|
}
|
|
if (pendingDevice && data.current_device_num === pendingDevice) {
|
|
pendingDevice = "";
|
|
deviceSelect.disabled = false;
|
|
}
|
|
renderDevices(data.devices || [], data.current_device_num || "");
|
|
renderTimings(data.timings || {});
|
|
renderDetections(data.detections || []);
|
|
});
|
|
|
|
ws.addEventListener("close", () => {
|
|
setConnection(false, "已断开");
|
|
setTimeout(connectWebSocket, 1500);
|
|
});
|
|
|
|
ws.addEventListener("error", () => {
|
|
setConnection(false, "连接错误");
|
|
ws.close();
|
|
});
|
|
}
|
|
|
|
deviceSelect.addEventListener("change", (event) => {
|
|
selectedDevice = event.target.value;
|
|
switchDevice(event.target.value).catch(() => {
|
|
pendingDevice = "";
|
|
devicesSignature = "";
|
|
setConnection(false, "切换失败");
|
|
deviceSelect.disabled = false;
|
|
});
|
|
});
|
|
|
|
connectWebSocket();
|