feat(demo): 模拟客户端与一键联调支持 1–4 路视频
- demo_orch: orchestrate-and-start 支持 video1 必填、video2–4 可选,扩展 camera/rtsp 参数 - demo_client/index.html: 路数选择、路 3/4 表单项、一键与 camera_ids 同步按路数 Made-with: Cursor
This commit is contained in:
@@ -239,14 +239,25 @@
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2>调试:两路视频(与一键联调 / 无真摄像头)</h2>
|
||||
<h2>调试:多路视频 1–4 路(与一键联调 / 无真摄像头)</h2>
|
||||
<p class="callout-ok small">
|
||||
在<strong>路1 / 路2</strong>选好视频、§4.1 勾选「一键联调」后点「开始手术」即可;服务端会起假 RTSP 并写 <code>VIDEO_RTSP_URLS_JSON_FILE</code>。无法使用一键时,请按 <code>scripts/demo_client/README.md</code> 在宿主机手跑
|
||||
在下方选好各路视频、第 4.1 节勾选「一键联调」后点「开始手术」即可;服务端会起假 RTSP 并写 <code>VIDEO_RTSP_URLS_JSON_FILE</code>。无法使用一键时,请按 <code>scripts/demo_client/README.md</code> 在宿主机手跑
|
||||
<code>fake_rtsp_from_file.py</code> 并配置环境变量。
|
||||
</p>
|
||||
<h3>两路视频(为 §4.1 一键选文件;两路 <code>RTSP_PATH</code> / <code>camera_id</code> 须与 API 配置一致,如 <code>demo1</code> / <code>demo2</code>)</h3>
|
||||
<div class="row" style="margin-top:10px; align-items:stretch; grid-template-columns:1fr 1fr">
|
||||
<div class="debug-stream" id="debug-stream-1" style="border:1px solid var(--border); border-radius:8px; padding:10px">
|
||||
<div class="row" style="margin-top:8px; max-width:28rem">
|
||||
<div>
|
||||
<label>模拟路数(1–4)</label>
|
||||
<select id="debug-stream-count">
|
||||
<option value="1">1 路</option>
|
||||
<option value="2" selected>2 路</option>
|
||||
<option value="3">3 路</option>
|
||||
<option value="4">4 路</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<h3>各路视频(为第 4.1 节一键选文件;每路 <code>RTSP_PATH</code> 须不同,<code>camera_id</code> 须与开录时一致)</h3>
|
||||
<div id="debug-streams-grid" class="row" style="margin-top:10px; align-items:stretch; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr))">
|
||||
<div class="debug-stream" id="debug-stream-1" data-stream-slot="1" style="border:1px solid var(--border); border-radius:8px; padding:10px">
|
||||
<h3 style="margin:0 0 8px; color:var(--accent)">路 1</h3>
|
||||
<label>视频(一键上传优先;可选手填本地路径作备注)</label>
|
||||
<input id="debug-vpath-1" type="text" placeholder="/path/a.mp4 或 ./a.mp4" />
|
||||
@@ -257,7 +268,7 @@
|
||||
</div>
|
||||
<div class="row" style="margin-top:8px">
|
||||
<div>
|
||||
<label>RTSP 路径名 <code>RTSP_PATH</code>(URL 最后一段,两路须不同,如 <code>demo1</code>)</label>
|
||||
<label>RTSP 路径名 <code>RTSP_PATH</code></label>
|
||||
<input id="debug-rpath-1" type="text" value="demo1" />
|
||||
</div>
|
||||
<div>
|
||||
@@ -266,7 +277,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="debug-stream" id="debug-stream-2" style="border:1px solid var(--border); border-radius:8px; padding:10px">
|
||||
<div class="debug-stream" id="debug-stream-2" data-stream-slot="2" style="border:1px solid var(--border); border-radius:8px; padding:10px">
|
||||
<h3 style="margin:0 0 8px; color:var(--accent)">路 2</h3>
|
||||
<label>视频(一键上传优先;可选手填本地路径作备注)</label>
|
||||
<input id="debug-vpath-2" type="text" placeholder="/path/b.mp4 或 ./b.mp4" />
|
||||
@@ -286,12 +297,52 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="debug-stream" id="debug-stream-3" data-stream-slot="3" style="border:1px solid var(--border); border-radius:8px; padding:10px; display:none">
|
||||
<h3 style="margin:0 0 8px; color:var(--accent)">路 3</h3>
|
||||
<label>视频(一键上传优先;可选手填本地路径作备注)</label>
|
||||
<input id="debug-vpath-3" type="text" placeholder="/path/c.mp4 或 ./c.mp4" />
|
||||
<div class="actions" style="margin-top:6px; align-items:center">
|
||||
<input type="file" id="debug-vfile-3" accept="video/*" hidden />
|
||||
<button type="button" class="secondary" id="btn-dbg-pick-3">选择…</button>
|
||||
<span id="debug-hint-3" class="small muted"></span>
|
||||
</div>
|
||||
<div class="row" style="margin-top:8px">
|
||||
<div>
|
||||
<label>RTSP 路径名 <code>RTSP_PATH</code></label>
|
||||
<input id="debug-rpath-3" type="text" value="demo3" />
|
||||
</div>
|
||||
<div>
|
||||
<label>camera_id</label>
|
||||
<input id="debug-cam-3" type="text" value="or-cam-03" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="debug-stream" id="debug-stream-4" data-stream-slot="4" style="border:1px solid var(--border); border-radius:8px; padding:10px; display:none">
|
||||
<h3 style="margin:0 0 8px; color:var(--accent)">路 4</h3>
|
||||
<label>视频(一键上传优先;可选手填本地路径作备注)</label>
|
||||
<input id="debug-vpath-4" type="text" placeholder="/path/d.mp4 或 ./d.mp4" />
|
||||
<div class="actions" style="margin-top:6px; align-items:center">
|
||||
<input type="file" id="debug-vfile-4" accept="video/*" hidden />
|
||||
<button type="button" class="secondary" id="btn-dbg-pick-4">选择…</button>
|
||||
<span id="debug-hint-4" class="small muted"></span>
|
||||
</div>
|
||||
<div class="row" style="margin-top:8px">
|
||||
<div>
|
||||
<label>RTSP 路径名 <code>RTSP_PATH</code></label>
|
||||
<input id="debug-rpath-4" type="text" value="demo4" />
|
||||
</div>
|
||||
<div>
|
||||
<label>camera_id</label>
|
||||
<input id="debug-cam-4" type="text" value="or-cam-04" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p id="debug-file-note" class="muted small" style="margin:8px 0 0">
|
||||
一键联调会<strong>直接上传</strong>你在此为路1/路2选择的文件。选文件时会把框内填成 <code>./文件名</code>,仅作展示;真正上传以文件选择器为准,无需在框里改路径。
|
||||
一键联调会<strong>直接上传</strong>你在此为各路选择的文件。选文件时会把框内填成 <code>./文件名</code>,仅作展示;真正上传以文件选择器为准,无需在框里改路径。
|
||||
</p>
|
||||
<div class="actions" style="margin-top:8px">
|
||||
<button type="button" class="secondary" id="btn-debug-apply-cams" title="把两路 camera_id 写进 §4.1 的 camera_ids">将 camera_id 填到开始手术</button>
|
||||
<button type="button" class="secondary" id="btn-debug-apply-cams" title="把当前已启用的各路 camera_id 写进开录的 camera_ids">将 camera_id 填到开始手术</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -312,7 +363,7 @@
|
||||
<p class="small muted" style="margin:8px 0 0">
|
||||
<label style="display:inline-flex;align-items:flex-start;gap:8px;cursor:pointer;max-width:52rem">
|
||||
<input type="checkbox" id="orch-oneclick" style="margin-top:2px" />
|
||||
<span><strong>一键联调</strong>:点下面按钮时上传 §「调试」里为<strong>路1/路2</strong>选好的两个视频,由监控服务在<strong>能执行 docker+ffmpeg 的环境</strong>里自动起假 RTSP、写 <code>VIDEO_RTSP_URLS_JSON_FILE</code> 并开录(需 <code>DEMO_ORCHESTRATOR_ENABLED=true</code> 且该文件为可写挂载;详见 README)。不勾选时仍为普通 JSON 开录(需自行先起假流)。</span>
|
||||
<span><strong>一键联调</strong>:点下面按钮时按「模拟路数」上传调试区为<strong>路 1…N</strong>选好的视频(1–4 路),由监控服务在<strong>能执行 docker+ffmpeg 的环境</strong>里自动起假 RTSP、写 <code>VIDEO_RTSP_URLS_JSON_FILE</code> 并开录(需 <code>DEMO_ORCHESTRATOR_ENABLED=true</code> 且该文件为可写挂载;详见 README)。不勾选时仍为普通 JSON 开录(需自行先起假流)。</span>
|
||||
</label>
|
||||
</p>
|
||||
<div class="actions">
|
||||
@@ -621,20 +672,35 @@
|
||||
const sid = ensureSurgeryId();
|
||||
if (!sid) return;
|
||||
if ($("orch-oneclick") && $("orch-oneclick").checked) {
|
||||
const f1 = $("debug-vfile-1").files[0];
|
||||
const f2 = $("debug-vfile-2").files[0];
|
||||
if (!f1 || !f2) {
|
||||
alert("请先在上方「调试」里为 路1 / 路2 各「选择…」一个视频文件。");
|
||||
const n = getDebugStreamCount();
|
||||
const files = [];
|
||||
for (let i = 1; i <= n; i++) {
|
||||
const f = $("debug-vfile-" + i).files && $("debug-vfile-" + i).files[0];
|
||||
files.push(f);
|
||||
}
|
||||
const missing = files.findIndex((x) => !x);
|
||||
if (missing >= 0) {
|
||||
alert("请先在上方「调试」里为 路 " + (missing + 1) + " 「选择…」一个视频文件(当前为 " + n + " 路)。");
|
||||
return;
|
||||
}
|
||||
const fd = new FormData();
|
||||
fd.append("video1", f1, f1.name);
|
||||
fd.append("video2", f2, f2.name);
|
||||
fd.append("surgery_id", sid);
|
||||
fd.append("camera_1", ($("debug-cam-1").value || "or-cam-01").trim() || "or-cam-01");
|
||||
fd.append("camera_2", ($("debug-cam-2").value || "or-cam-02").trim() || "or-cam-02");
|
||||
fd.append("rtsp_path_1", ($("debug-rpath-1").value || "demo1").trim() || "demo1");
|
||||
fd.append("rtsp_path_2", ($("debug-rpath-2").value || "demo2").trim() || "demo2");
|
||||
fd.append("video1", files[0], files[0].name);
|
||||
if (n >= 2) fd.append("video2", files[1], files[1].name);
|
||||
if (n >= 3) fd.append("video3", files[2], files[2].name);
|
||||
if (n >= 4) fd.append("video4", files[3], files[3].name);
|
||||
const defCams = ["or-cam-01", "or-cam-02", "or-cam-03", "or-cam-04"];
|
||||
const defRp = ["demo1", "demo2", "demo3", "demo4"];
|
||||
for (let i = 1; i <= 4; i++) {
|
||||
fd.append(
|
||||
"camera_" + i,
|
||||
($("debug-cam-" + i).value || defCams[i - 1]).trim() || defCams[i - 1],
|
||||
);
|
||||
fd.append(
|
||||
"rtsp_path_" + i,
|
||||
($("debug-rpath-" + i).value || defRp[i - 1]).trim() || defRp[i - 1],
|
||||
);
|
||||
}
|
||||
fd.append("candidate_consumables_json", JSON.stringify([...tags]));
|
||||
const { res, body } = await apiMultipart("/internal/demo/orchestrate-and-start", fd);
|
||||
if (!res.ok) {
|
||||
@@ -1194,27 +1260,56 @@
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// Debug: two streams for one-click upload (路1/路2)
|
||||
// Debug: 1–4 streams for one-click upload
|
||||
// ============================================================
|
||||
$("btn-dbg-pick-1").onclick = () => $("debug-vfile-1").click();
|
||||
$("debug-vfile-1").addEventListener("change", (e) => {
|
||||
const f = e.target.files && e.target.files[0];
|
||||
if (!f) return;
|
||||
$("debug-vpath-1").value = "./" + f.name;
|
||||
$("debug-hint-1").textContent = "已选: " + f.name;
|
||||
});
|
||||
$("btn-dbg-pick-2").onclick = () => $("debug-vfile-2").click();
|
||||
$("debug-vfile-2").addEventListener("change", (e) => {
|
||||
const f = e.target.files && e.target.files[0];
|
||||
if (!f) return;
|
||||
$("debug-vpath-2").value = "./" + f.name;
|
||||
$("debug-hint-2").textContent = "已选: " + f.name;
|
||||
});
|
||||
function getDebugStreamCount() {
|
||||
const sel = $("debug-stream-count");
|
||||
const v = sel ? parseInt(sel.value, 10) : 2;
|
||||
if (v === 1 || v === 2 || v === 3 || v === 4) return v;
|
||||
return 2;
|
||||
}
|
||||
|
||||
function applyDebugStreamVisibility() {
|
||||
const n = getDebugStreamCount();
|
||||
for (let i = 1; i <= 4; i++) {
|
||||
const el = $("debug-stream-" + i);
|
||||
if (!el) continue;
|
||||
el.style.display = i <= n ? "block" : "none";
|
||||
}
|
||||
}
|
||||
|
||||
if ($("debug-stream-count")) {
|
||||
$("debug-stream-count").addEventListener("change", () => {
|
||||
applyDebugStreamVisibility();
|
||||
});
|
||||
applyDebugStreamVisibility();
|
||||
}
|
||||
|
||||
for (let i = 1; i <= 4; i++) {
|
||||
const pick = $("btn-dbg-pick-" + i);
|
||||
const vfile = $("debug-vfile-" + i);
|
||||
const vpath = $("debug-vpath-" + i);
|
||||
const hint = $("debug-hint-" + i);
|
||||
if (pick && vfile) {
|
||||
pick.onclick = () => vfile.click();
|
||||
vfile.addEventListener("change", (e) => {
|
||||
const f = e.target.files && e.target.files[0];
|
||||
if (!f) return;
|
||||
vpath.value = "./" + f.name;
|
||||
hint.textContent = "已选: " + f.name;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$("btn-debug-apply-cams").onclick = () => {
|
||||
const a = ($("debug-cam-1").value || "or-cam-01").trim() || "or-cam-01";
|
||||
const b = ($("debug-cam-2").value || "or-cam-02").trim() || "or-cam-02";
|
||||
$("camera-ids").value = a + "," + b;
|
||||
const defCams = ["or-cam-01", "or-cam-02", "or-cam-03", "or-cam-04"];
|
||||
const n = getDebugStreamCount();
|
||||
const parts = [];
|
||||
for (let i = 1; i <= n; i++) {
|
||||
const a = ($("debug-cam-" + i).value || defCams[i - 1]).trim() || defCams[i - 1];
|
||||
parts.push(a);
|
||||
}
|
||||
$("camera-ids").value = parts.join(",");
|
||||
};
|
||||
|
||||
(function setupDebugVideoDrop() {
|
||||
@@ -1243,8 +1338,9 @@
|
||||
$(hintId).textContent = "已选: " + f.name + "(拖放)";
|
||||
});
|
||||
}
|
||||
bindStreamCard($("debug-stream-1"), "debug-vpath-1", "debug-hint-1");
|
||||
bindStreamCard($("debug-stream-2"), "debug-vpath-2", "debug-hint-2");
|
||||
for (let i = 1; i <= 4; i++) {
|
||||
bindStreamCard($("debug-stream-" + i), "debug-vpath-" + i, "debug-hint-" + i);
|
||||
}
|
||||
})();
|
||||
|
||||
// ============================================================
|
||||
|
||||
Reference in New Issue
Block a user