Implement synchronized LED wall output
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>LED Wall Control</title>
|
||||
<title>LED 大屏控制台</title>
|
||||
<link rel="stylesheet" href="/static/admin/styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
@@ -22,7 +22,7 @@
|
||||
<section class="grid">
|
||||
<section class="panel scenes">
|
||||
<div class="panelHead">
|
||||
<h2>场景</h2>
|
||||
<h2>场景切换</h2>
|
||||
<button id="refreshBtn">刷新</button>
|
||||
</div>
|
||||
<div id="sceneList" class="sceneList"></div>
|
||||
@@ -35,6 +35,7 @@
|
||||
<div class="linkGrid">
|
||||
<a href="/output/left" target="_blank">打开左输出</a>
|
||||
<a href="/output/right" target="_blank">打开右输出</a>
|
||||
<a href="/api/sync/status" target="_blank">同步状态</a>
|
||||
<a href="/docs" target="_blank">API 文档</a>
|
||||
</div>
|
||||
<div id="syncStatus" class="syncStatus"></div>
|
||||
|
||||
@@ -48,9 +48,10 @@ function renderScenes() {
|
||||
function renderSyncStatus() {
|
||||
if (!syncSnapshot) return;
|
||||
const nodes = syncSnapshot.nodes || [];
|
||||
const commands = (syncSnapshot.commands || []).slice(-4).reverse();
|
||||
const commands = (syncSnapshot.commands || []).slice(-5).reverse();
|
||||
const rows = [];
|
||||
rows.push(`<div class="syncRow"><span>输出节点</span><strong>${nodes.length}/${syncSnapshot.target_tiles.length}</strong></div>`);
|
||||
|
||||
for (const node of nodes) {
|
||||
const fps = node.fps == null ? "--" : node.fps.toFixed(1);
|
||||
const frame = node.frame_time_ms == null ? "--" : `${node.frame_time_ms.toFixed(1)}ms`;
|
||||
@@ -58,10 +59,12 @@ function renderSyncStatus() {
|
||||
const offset = node.clock_offset_ms == null ? "--" : `${Math.round(node.clock_offset_ms)}ms`;
|
||||
rows.push(`<div class="syncRow"><span>${node.tile_id} ${node.node_id.slice(0, 16)}</span><span>${fps}fps / ${frame} / rtt ${rtt} / offset ${offset}</span></div>`);
|
||||
}
|
||||
|
||||
for (const command of commands) {
|
||||
const ackText = command.acks.map((ack) => `${ack.tile_id}:${ack.status}`).join(", ") || "等待 ACK";
|
||||
rows.push(`<div class="syncRow"><span>${command.command_type} ${command.command_id.slice(0, 8)}</span><span>${command.complete ? "完成" : "进行中"} | ${ackText}</span></div>`);
|
||||
}
|
||||
|
||||
syncStatus.innerHTML = rows.join("");
|
||||
}
|
||||
|
||||
@@ -127,10 +130,11 @@ function connectWs() {
|
||||
renderScenes();
|
||||
loadSyncStatus();
|
||||
}
|
||||
if (["ack", "heartbeat"].includes(message.type)) {
|
||||
if (message.type === "ack") {
|
||||
wsText.textContent = `${message.payload.tile_id || "node"} ${message.payload.status}`;
|
||||
}
|
||||
if (message.type === "ack") {
|
||||
wsText.textContent = `${message.payload.tile_id || "node"} ${message.payload.status}`;
|
||||
loadSyncStatus();
|
||||
}
|
||||
if (message.type === "heartbeat") {
|
||||
loadSyncStatus();
|
||||
}
|
||||
});
|
||||
@@ -148,4 +152,4 @@ document.querySelectorAll("[data-action]").forEach((button) => {
|
||||
loadScenes();
|
||||
loadSyncStatus();
|
||||
connectWs();
|
||||
setInterval(loadSyncStatus, 3000);
|
||||
setInterval(loadSyncStatus, 2500);
|
||||
|
||||
Reference in New Issue
Block a user