You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
sopSystem/test_websocket_frontend(2)....

432 lines
12 KiB
HTML

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>SOP WebSocket 前端测试</title>
<style>
body {
margin: 0;
font-family: Arial, "Microsoft YaHei", sans-serif;
background: #f5f7fb;
color: #1f2937;
}
.container {
max-width: 1100px;
margin: 0 auto;
padding: 24px;
}
.card {
background: #fff;
border-radius: 12px;
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.08);
padding: 20px;
margin-bottom: 16px;
}
h1 {
margin: 0 0 16px;
font-size: 24px;
}
label {
display: block;
margin: 10px 0 6px;
font-weight: 600;
}
input, button {
font-size: 14px;
}
input[type="text"], input[type="number"] {
width: 100%;
box-sizing: border-box;
border: 1px solid #d1d5db;
border-radius: 8px;
padding: 10px;
}
input[type="file"] {
width: 100%;
padding: 10px 0;
}
.grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 12px;
}
.buttons {
display: flex;
gap: 10px;
margin-top: 16px;
flex-wrap: wrap;
}
button {
border: 0;
border-radius: 8px;
padding: 10px 16px;
cursor: pointer;
color: #fff;
background: #2563eb;
}
button:disabled {
cursor: not-allowed;
background: #9ca3af;
}
.danger {
background: #dc2626;
}
.status {
padding: 10px 12px;
border-radius: 8px;
background: #eef2ff;
color: #3730a3;
margin-bottom: 12px;
white-space: pre-wrap;
}
.progress-wrap {
height: 16px;
background: #e5e7eb;
border-radius: 999px;
overflow: hidden;
}
.progress-bar {
height: 100%;
width: 0%;
background: linear-gradient(90deg, #22c55e, #16a34a);
transition: width 0.2s;
}
.counts {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 8px;
}
.count-item {
padding: 10px;
border-radius: 8px;
background: #f3f4f6;
display: flex;
justify-content: space-between;
}
.log {
height: 280px;
overflow: auto;
background: #111827;
color: #d1d5db;
border-radius: 8px;
padding: 12px;
font-family: Consolas, monospace;
font-size: 13px;
white-space: pre-wrap;
}
video {
width: 100%;
max-height: 520px;
background: #000;
border-radius: 8px;
}
a {
color: #2563eb;
word-break: break-all;
}
</style>
</head>
<body>
<div class="container">
<div class="card">
<h1>SOP 视频检测 WebSocket 前端测试</h1>
<div id="status" class="status">未连接</div>
<label>WebSocket 地址</label>
<input id="wsUrl" type="text" value="ws://10.21.221.41:8000/ws/detect" />
<label>HTTP 服务地址,用于播放结果视频</label>
<input id="httpBase" type="text" value="http://10.21.221.41:8000" />
<label>选择要上传的视频</label>
<input id="videoFile" type="file" accept="video/*" />
<div class="grid">
<div>
<label>conf</label>
<input id="conf" type="number" value="0.5" min="0" max="1" step="0.01" />
</div>
<div>
<label>iou</label>
<input id="iou" type="number" value="0.45" min="0" max="1" step="0.01" />
</div>
<div>
<label>skip</label>
<input id="skip" type="number" value="5" min="1" step="1" />
</div>
<div>
<label>max_frames0 表示完整视频</label>
<input id="maxFrames" type="number" value="0" min="0" step="1" />
</div>
</div>
<div class="buttons">
<button id="startBtn">上传并开始检测</button>
<button id="pauseBtn" disabled>暂停处理</button>
<button id="endBtn" class="danger" disabled>结束检测</button>
<button id="stopBtn" class="danger" disabled>关闭连接</button>
</div>
</div>
<div class="card">
<h2>实时进度</h2>
<div class="progress-wrap"><div id="progressBar" class="progress-bar"></div></div>
<p id="progressText">0%</p>
</div>
<div class="card">
<h2>实时计数</h2>
<div id="counts" class="counts">暂无计数</div>
</div>
<div class="card">
<h2>实时日志</h2>
<div id="log" class="log"></div>
</div>
<div class="card">
<h2>处理后视频</h2>
<video id="resultVideo" controls></video>
<p id="resultLinks"></p>
</div>
</div>
<script>
const statusEl = document.getElementById("status");
const logEl = document.getElementById("log");
const countsEl = document.getElementById("counts");
const progressBar = document.getElementById("progressBar");
const progressText = document.getElementById("progressText");
const resultVideo = document.getElementById("resultVideo");
const resultLinks = document.getElementById("resultLinks");
const startBtn = document.getElementById("startBtn");
const pauseBtn = document.getElementById("pauseBtn");
const endBtn = document.getElementById("endBtn");
const stopBtn = document.getElementById("stopBtn");
let ws = null;
let paused = false;
let ending = false;
function setStatus(text) {
statusEl.textContent = text;
}
function appendLog(text) {
logEl.textContent += text + "\n";
logEl.scrollTop = logEl.scrollHeight;
}
function renderCounts(counts) {
const entries = Object.entries(counts || {});
if (!entries.length) {
countsEl.textContent = "暂无计数";
return;
}
countsEl.innerHTML = entries.map(([name, value]) => (
`<div class="count-item"><span>${name}</span><strong>${value}</strong></div>`
)).join("");
}
function renderProgress(msg) {
const progress = Number(msg.progress || 0);
progressBar.style.width = `${Math.max(0, Math.min(100, progress))}%`;
progressText.textContent = `${progress.toFixed(1)}% | ${msg.frame || 0}/${msg.total_frames || 0}`;
}
async function sendFileInChunks(socket, file) {
const chunkSize = 1024 * 1024;
let offset = 0;
while (offset < file.size) {
const chunk = file.slice(offset, offset + chunkSize);
const buffer = await chunk.arrayBuffer();
socket.send(buffer);
offset += chunkSize;
}
}
startBtn.onclick = async () => {
const file = document.getElementById("videoFile").files[0];
const wsUrl = document.getElementById("wsUrl").value.trim();
if (!file) {
alert("请先选择视频文件");
return;
}
if (!wsUrl.startsWith("ws://") && !wsUrl.startsWith("wss://")) {
alert("WebSocket 地址必须以 ws:// 或 wss:// 开头");
return;
}
logEl.textContent = "";
resultLinks.innerHTML = "";
resultVideo.removeAttribute("src");
resultVideo.load();
renderCounts({});
renderProgress({ progress: 0, frame: 0, total_frames: 0 });
startBtn.disabled = true;
pauseBtn.disabled = true;
pauseBtn.textContent = "暂停处理";
paused = false;
ending = false;
endBtn.disabled = true;
stopBtn.disabled = false;
setStatus("正在连接 WebSocket...");
ws = new WebSocket(wsUrl);
ws.binaryType = "arraybuffer";
ws.onopen = () => {
setStatus("已连接,正在打开算法开关...");
ws.send(JSON.stringify({ type: "switch", enabled: true }));
};
ws.onmessage = async (event) => {
const msg = JSON.parse(event.data);
if (msg.type === "ready") {
appendLog("服务端 ready");
return;
}
if (msg.type === "switch") {
appendLog("算法开关已开启");
ws.send(JSON.stringify({
type: "start",
filename: file.name,
options: {
conf: Number(document.getElementById("conf").value),
iou: Number(document.getElementById("iou").value),
skip: Number(document.getElementById("skip").value),
imgsz: 640,
max_frames: Number(document.getElementById("maxFrames").value),
save_video: true
}
}));
return;
}
if (msg.type === "upload_started") {
setStatus("正在上传视频...");
appendLog("开始上传视频");
await sendFileInChunks(ws, file);
ws.send(JSON.stringify({ type: "end" }));
appendLog("视频上传完成,等待服务端处理");
return;
}
if (msg.type === "processing_started") {
setStatus("服务端正在处理视频...");
appendLog("检测开始");
pauseBtn.disabled = false;
endBtn.disabled = false;
return;
}
if (msg.type === "stopping") {
ending = true;
pauseBtn.disabled = true;
endBtn.disabled = true;
setStatus(msg.message || "正在结束检测...");
appendLog(msg.message || "已请求结束检测");
return;
}
if (msg.type === "cancelled") {
ending = true;
pauseBtn.disabled = true;
endBtn.disabled = true;
setStatus(msg.message || "检测已结束,正在生成当前结果...");
appendLog(msg.message || "检测已结束");
return;
}
if (msg.type === "pause") {
paused = Boolean(msg.paused);
pauseBtn.textContent = paused ? "恢复处理" : "暂停处理";
setStatus(paused ? "检测已暂停,服务仍在运行" : "服务端正在处理视频...");
appendLog(msg.message || (paused ? "检测已暂停" : "检测已恢复"));
return;
}
if (msg.type === "progress") {
renderProgress(msg);
return;
}
if (msg.type === "counts") {
renderCounts(msg.counts);
return;
}
if (msg.type === "log") {
appendLog(msg.message || "");
return;
}
if (msg.type === "done") {
const httpBase = document.getElementById("httpBase").value.replace(/\/$/, "");
const videoUrl = httpBase + msg.output_video_url;
const reportUrl = httpBase + msg.report_url;
setStatus("检测完成");
pauseBtn.disabled = true;
endBtn.disabled = true;
renderCounts(msg.counts || {});
resultVideo.src = videoUrl;
resultLinks.innerHTML = `结果视频:<a href="${videoUrl}" target="_blank">${videoUrl}</a><br>` +
`检测报告:<a href="${reportUrl}" target="_blank">${reportUrl}</a>`;
appendLog("检测完成");
ws.close();
return;
}
if (msg.type === "error") {
setStatus("服务端错误:" + msg.message);
appendLog("错误:" + msg.message);
pauseBtn.disabled = true;
endBtn.disabled = true;
ws.close();
}
};
ws.onerror = () => {
setStatus("WebSocket 连接失败,请检查服务地址、端口、防火墙和服务是否以 0.0.0.0 启动");
};
ws.onclose = () => {
startBtn.disabled = false;
pauseBtn.disabled = true;
pauseBtn.textContent = "暂停处理";
endBtn.disabled = true;
stopBtn.disabled = true;
};
};
pauseBtn.onclick = () => {
if (!ws || ws.readyState !== WebSocket.OPEN) return;
paused = !paused;
ws.send(JSON.stringify({ type: "pause", paused }));
pauseBtn.textContent = paused ? "恢复处理" : "暂停处理";
};
endBtn.onclick = () => {
if (!ws || ws.readyState !== WebSocket.OPEN || ending) return;
ending = true;
pauseBtn.disabled = true;
endBtn.disabled = true;
setStatus("已发送结束检测请求,等待服务端生成当前结果...");
appendLog("前端请求结束检测");
ws.send(JSON.stringify({ type: "stop" }));
};
stopBtn.onclick = () => {
if (ws) ws.close();
setStatus("连接已关闭");
};
</script>
</body>
</html>