// 视频实时监控页面脚本
let streams = [];
let detectionStatus = {};
let processedFrames = {}; // 存储处理后的帧
// 加载视频流列表
async function loadStreams() {
try {
const res = await fetch('/streams');
const data = await res.json();
if (data.code === 0) {
streams = data.data;
document.getElementById('cameraCount').textContent = streams.length;
renderVideos();
}
} catch (e) {
addLog('加载视频流失败: ' + e.message, 'error');
}
}
// 加载检测状态
async function loadStatus() {
try {
const res = await fetch('/status');
const data = await res.json();
if (data.code === 0) {
document.getElementById('checkInterval').textContent = data.data.check_interval;
}
} catch (e) {
console.error('加载状态失败:', e);
}
}
// 渲染视频卡片
function renderVideos() {
const container = document.getElementById('videoContainer');
if (streams.length === 0) {
container.innerHTML = '
暂无视频流
';
return;
}
container.innerHTML = streams.map(stream => `
检测状态
正常
最后检测
-
码率
${formatBytes(stream.bytesSpeed)}/s
协议
JPEG帧
`).join('');
// 初始化所有视频流
streams.forEach(stream => initVideo(stream));
}
// 切换原始/处理后画面
function toggleProcess(streamKey, type) {
const rawFrame = document.getElementById(`frame-raw-${streamKey}`);
const processedFrame = document.getElementById(`frame-processed-${streamKey}`);
const rawBtn = document.getElementById(`btn-raw-${streamKey}`);
const processedBtn = document.getElementById(`btn-processed-${streamKey}`);
const stream = streams.find(s => s.key === streamKey);
if (!stream) return;
if (type === 'raw') {
rawFrame.classList.remove('hidden');
processedFrame.classList.add('hidden');
rawBtn.classList.add('active');
processedBtn.classList.remove('active');
// 立即刷新原始帧
rawFrame.src = `/proxy/zlm/snap?app=${stream.app}&stream=${stream.stream}&t=${Date.now()}`;
} else {
rawFrame.classList.add('hidden');
processedFrame.classList.remove('hidden');
rawBtn.classList.remove('active');
processedBtn.classList.add('active');
// 立即刷新处理后帧
processedFrame.src = `/proxy/zlm/snap?app=${stream.app}&stream=${stream.stream}&t=${Date.now()}&processed=1`;
}
}
// 初始化单个视频
function initVideo(stream) {
const rawFrame = document.getElementById(`frame-raw-${stream.key}`);
const processedFrame = document.getElementById(`frame-processed-${stream.key}`);
const canvas = document.getElementById(`roi-${stream.key}`);
// 加载并绘制ROI区域
loadAndDrawROI(stream.key, canvas);
// 开始拉取视频帧
startFramePolling(stream.key, rawFrame, processedFrame);
}
// 轮询获取视频帧
function startFramePolling(streamKey, rawFrame, processedFrame) {
// 使用 ZLM 的截图API获取帧
const stream = streams.find(s => s.key === streamKey);
if (!stream) {
console.error('未找到流:', streamKey);
return;
}
// 构建截图URL (使用 ZLM 的 getSnap 接口)
const snapUrl = `/proxy/zlm/snap?app=${stream.app}&stream=${stream.stream}`;
console.log('开始轮询截图:', streamKey, 'URL:', snapUrl);
// 添加图片加载错误处理
rawFrame.onerror = function() {
console.error('原始帧加载失败:', streamKey);
};
processedFrame.onerror = function() {
console.error('处理后帧加载失败:', streamKey);
};
rawFrame.onload = function() {
console.log('原始帧加载成功:', streamKey);
};
// 定期刷新帧
let lastTimestamp = 0;
const refreshFrame = () => {
try {
// 添加时间戳防止缓存
const timestamp = Date.now();
if (timestamp - lastTimestamp < 100) {
return; // 防止过于频繁的刷新
}
lastTimestamp = timestamp;
// 根据当前显示状态刷新对应的帧
if (!rawFrame.classList.contains('hidden')) {
rawFrame.src = `${snapUrl}&t=${timestamp}`;
}
if (!processedFrame.classList.contains('hidden')) {
processedFrame.src = `${snapUrl}&t=${timestamp}&processed=1`;
}
} catch (e) {
console.error('获取帧失败:', e);
}
};
// 先加载一次
refreshFrame();
// 每500ms刷新一次 (2fps) - 降低频率避免过载
const interval = setInterval(refreshFrame, 500);
// 保存interval以便清理
rawFrame.dataset.interval = interval;
}
// 加载并绘制ROI
async function loadAndDrawROI(streamKey, canvas) {
try {
const res = await fetch('/getRoi?stream_key=' + streamKey);
const data = await res.json();
if (data.code === 0 && data.data) {
drawROI(canvas, data.data);
}
} catch (e) {
console.error('加载ROI失败:', e);
}
}
// 绘制ROI区域
function drawROI(canvas, points) {
const ctx = canvas.getContext('2d');
const rect = canvas.getBoundingClientRect();
canvas.width = rect.width;
canvas.height = rect.height;
// 坐标转换(假设原始坐标是640x480)
const scaleX = canvas.width / 640;
const scaleY = canvas.height / 480;
ctx.strokeStyle = '#e94560';
ctx.lineWidth = 2;
ctx.fillStyle = 'rgba(233, 69, 96, 0.2)';
ctx.beginPath();
points.forEach((p, i) => {
const x = p.x * scaleX;
const y = p.y * scaleY;
if (i === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
});
ctx.closePath();
ctx.fill();
ctx.stroke();
}
// 格式化字节
function formatBytes(bytes) {
if (!bytes || bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
// 添加日志
function addLog(message, type = 'info') {
const logContent = document.getElementById('logContent');
const time = new Date().toLocaleTimeString();
const entry = document.createElement('div');
entry.className = 'log-entry';
entry.innerHTML = `
${time}
${message}
`;
logContent.insertBefore(entry, logContent.firstChild);
if (logContent.children.length > 50) {
logContent.removeChild(logContent.lastChild);
}
}
// 模拟检测状态更新(实际应通过WebSocket)
function simulateDetection() {
streams.forEach(stream => {
const isDetected = Math.random() > 0.9;
const badge = document.getElementById('badge-' + stream.key);
const detectStatus = document.getElementById('detect-' + stream.key);
const lastCheck = document.getElementById('last-' + stream.key);
if (badge && detectStatus && lastCheck) {
if (isDetected) {
badge.className = 'detection-badge detected';
badge.textContent = '有人';
detectStatus.className = 'info-value detected';
detectStatus.textContent = '检测到人员';
} else {
badge.className = 'detection-badge idle';
badge.textContent = '无人';
detectStatus.className = 'info-value';
detectStatus.textContent = '正常';
}
lastCheck.textContent = new Date().toLocaleTimeString();
}
});
}
// 初始化
loadStreams();
loadStatus();
setInterval(loadStreams, 30000); // 30秒刷新流列表
setInterval(simulateDetection, 1000); // 1秒更新检测状态
// 窗口大小改变时重绘ROI
window.addEventListener('resize', () => {
streams.forEach(stream => {
const canvas = document.getElementById('roi-' + stream.key);
if (canvas) loadAndDrawROI(stream.key, canvas);
});
});