Yolov/templates/flv2.html

814 lines
26 KiB
HTML
Raw Normal View History

2025-11-26 13:55:04 +08:00
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
2025-12-11 13:41:07 +08:00
<title>视频流播放器</title>
<script src="https://cdn.bootcdn.net/ajax/libs/flv.js/1.6.2/flv.min.js"></script>
2025-11-26 13:55:04 +08:00
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
2025-12-11 13:41:07 +08:00
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #1a2a3a, #0d1825);
color: white;
min-height: 100vh;
padding: 20px;
2025-11-26 13:55:04 +08:00
}
.container {
2025-12-11 13:41:07 +08:00
max-width: 1400px;
2025-11-26 13:55:04 +08:00
margin: 0 auto;
}
header {
2025-12-11 13:41:07 +08:00
text-align: center;
2025-11-26 13:55:04 +08:00
margin-bottom: 30px;
2025-12-11 13:41:07 +08:00
padding: 20px;
background: rgba(255, 255, 255, 0.1);
border-radius: 10px;
backdrop-filter: blur(10px);
2025-11-26 13:55:04 +08:00
}
h1 {
font-size: 2.5rem;
2025-12-11 13:41:07 +08:00
margin-bottom: 10px;
background: linear-gradient(90deg, #3498db, #9b59b6);
2025-11-26 13:55:04 +08:00
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
2025-12-11 13:41:07 +08:00
.subtitle {
font-size: 1.2rem;
opacity: 0.8;
2025-11-26 13:55:04 +08:00
}
2025-12-11 13:41:07 +08:00
.player-container {
display: grid;
grid-template-columns: 2fr 1fr;
2025-11-26 13:55:04 +08:00
gap: 20px;
2025-12-11 13:41:07 +08:00
margin-bottom: 30px;
2025-11-26 13:55:04 +08:00
}
2025-12-11 13:41:07 +08:00
@media (max-width: 1024px) {
.player-container {
grid-template-columns: 1fr;
}
2025-11-26 13:55:04 +08:00
}
2025-12-11 13:41:07 +08:00
.video-section {
background: rgba(0, 0, 0, 0.5);
border-radius: 10px;
overflow: hidden;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
2025-11-26 13:55:04 +08:00
}
2025-12-11 13:41:07 +08:00
.video-wrapper {
position: relative;
width: 100%;
padding-bottom: 56.25%; /* 16:9 Aspect Ratio */
background: #000;
2025-11-26 13:55:04 +08:00
}
2025-12-11 13:41:07 +08:00
#videoElement {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: contain;
2025-11-26 13:55:04 +08:00
}
2025-12-11 13:41:07 +08:00
.video-controls {
padding: 15px;
background: rgba(255, 255, 255, 0.1);
2025-11-26 13:55:04 +08:00
display: flex;
2025-12-11 13:41:07 +08:00
justify-content: space-between;
2025-11-26 13:55:04 +08:00
align-items: center;
}
2025-12-11 13:41:07 +08:00
.control-btn {
background: #3498db;
color: white;
2025-11-26 13:55:04 +08:00
border: none;
2025-12-11 13:41:07 +08:00
padding: 10px 20px;
border-radius: 5px;
2025-11-26 13:55:04 +08:00
cursor: pointer;
2025-12-11 13:41:07 +08:00
font-size: 1rem;
display: flex;
align-items: center;
gap: 8px;
transition: all 0.3s ease;
2025-11-26 13:55:04 +08:00
}
2025-12-11 13:41:07 +08:00
.control-btn:hover {
background: #2980b9;
transform: translateY(-2px);
2025-11-26 13:55:04 +08:00
}
2025-12-11 13:41:07 +08:00
.control-btn:disabled {
background: #7f8c8d;
cursor: not-allowed;
transform: none;
2025-11-26 13:55:04 +08:00
}
2025-12-11 13:41:07 +08:00
.video-info {
font-size: 0.9rem;
opacity: 0.8;
2025-11-26 13:55:04 +08:00
}
2025-12-11 13:41:07 +08:00
.info-panel {
background: rgba(255, 255, 255, 0.1);
border-radius: 10px;
padding: 20px;
backdrop-filter: blur(10px);
2025-11-26 13:55:04 +08:00
display: flex;
2025-12-11 13:41:07 +08:00
flex-direction: column;
gap: 20px;
2025-11-26 13:55:04 +08:00
}
2025-12-11 13:41:07 +08:00
.info-card {
background: rgba(255, 255, 255, 0.05);
2025-11-26 13:55:04 +08:00
border-radius: 8px;
2025-12-11 13:41:07 +08:00
padding: 15px;
border-left: 4px solid #3498db;
2025-11-26 13:55:04 +08:00
}
2025-12-11 13:41:07 +08:00
.info-card h3 {
font-size: 1.2rem;
margin-bottom: 10px;
color: #3498db;
2025-11-26 13:55:04 +08:00
}
2025-12-11 13:41:07 +08:00
.info-row {
display: flex;
justify-content: space-between;
padding: 5px 0;
border-bottom: 1px dashed rgba(255, 255, 255, 0.1);
2025-11-26 13:55:04 +08:00
}
2025-12-11 13:41:07 +08:00
.info-label {
opacity: 0.7;
2025-11-26 13:55:04 +08:00
}
2025-12-11 13:41:07 +08:00
.info-value {
font-weight: 600;
2025-11-26 13:55:04 +08:00
}
2025-12-11 13:41:07 +08:00
.detection-list {
2025-11-26 13:55:04 +08:00
max-height: 300px;
overflow-y: auto;
}
.detection-item {
2025-12-11 13:41:07 +08:00
background: rgba(52, 152, 219, 0.1);
border-radius: 5px;
padding: 10px;
margin-bottom: 8px;
border-left: 3px solid #3498db;
2025-11-26 13:55:04 +08:00
}
.detection-header {
display: flex;
justify-content: space-between;
2025-12-11 13:41:07 +08:00
margin-bottom: 5px;
2025-11-26 13:55:04 +08:00
}
.detection-class {
2025-12-11 13:41:07 +08:00
font-weight: 600;
color: #3498db;
2025-11-26 13:55:04 +08:00
}
.detection-confidence {
2025-12-11 13:41:07 +08:00
background: #3498db;
color: white;
padding: 2px 8px;
border-radius: 10px;
font-size: 0.8rem;
2025-11-26 13:55:04 +08:00
}
2025-12-11 13:41:07 +08:00
.detection-box {
font-size: 0.85rem;
opacity: 0.8;
2025-11-26 13:55:04 +08:00
}
2025-12-11 13:41:07 +08:00
.stream-controls {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-bottom: 30px;
2025-11-26 13:55:04 +08:00
}
2025-12-11 13:41:07 +08:00
.input-group {
display: flex;
flex-direction: column;
gap: 5px;
2025-11-26 13:55:04 +08:00
}
2025-12-11 13:41:07 +08:00
.input-group label {
2025-11-26 13:55:04 +08:00
font-size: 0.9rem;
2025-12-11 13:41:07 +08:00
opacity: 0.8;
2025-11-26 13:55:04 +08:00
}
2025-12-11 13:41:07 +08:00
.input-group input {
padding: 12px 15px;
2025-11-26 13:55:04 +08:00
border: none;
2025-12-11 13:41:07 +08:00
border-radius: 5px;
background: rgba(255, 255, 255, 0.1);
color: white;
font-size: 1rem;
2025-11-26 13:55:04 +08:00
}
2025-12-11 13:41:07 +08:00
.input-group input:focus {
outline: none;
background: rgba(255, 255, 255, 0.2);
2025-11-26 13:55:04 +08:00
}
2025-12-11 13:41:07 +08:00
.empty-state {
text-align: center;
padding: 40px 20px;
opacity: 0.5;
2025-11-26 13:55:04 +08:00
}
2025-12-11 13:41:07 +08:00
.empty-state i {
font-size: 3rem;
margin-bottom: 15px;
2025-11-26 13:55:04 +08:00
}
2025-12-11 13:41:07 +08:00
/* 自定义滚动条 */
::-webkit-scrollbar {
width: 8px;
2025-11-26 13:55:04 +08:00
}
2025-12-11 13:41:07 +08:00
::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.1);
border-radius: 4px;
2025-11-26 13:55:04 +08:00
}
2025-12-11 13:41:07 +08:00
::-webkit-scrollbar-thumb {
background: #3498db;
2025-11-26 13:55:04 +08:00
border-radius: 4px;
}
2025-12-11 13:41:07 +08:00
::-webkit-scrollbar-thumb:hover {
background: #2980b9;
2025-11-26 13:55:04 +08:00
}
2025-12-11 13:41:07 +08:00
/* 加载动画 */
.loader {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
border-top-color: white;
animation: spin 1s ease-in-out infinite;
2025-11-26 13:55:04 +08:00
}
2025-12-11 13:41:07 +08:00
@keyframes spin {
to { transform: rotate(360deg); }
2025-11-26 13:55:04 +08:00
}
2025-12-11 13:41:07 +08:00
.status-indicator {
2025-11-26 13:55:04 +08:00
display: flex;
2025-12-11 13:41:07 +08:00
align-items: center;
gap: 8px;
2025-11-26 13:55:04 +08:00
}
2025-12-11 13:41:07 +08:00
.status-dot {
width: 10px;
height: 10px;
border-radius: 50%;
background: #e74c3c;
animation: pulse 2s infinite;
2025-11-26 13:55:04 +08:00
}
2025-12-11 13:41:07 +08:00
.status-dot.connected {
background: #27ae60;
2025-11-26 13:55:04 +08:00
}
2025-12-11 13:41:07 +08:00
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.5; }
100% { opacity: 1; }
2025-11-26 13:55:04 +08:00
}
2025-12-11 13:41:07 +08:00
.task-selector {
display: flex;
gap: 10px;
margin-bottom: 20px;
overflow-x: auto;
padding: 10px 0;
2025-11-26 13:55:04 +08:00
}
2025-12-11 13:41:07 +08:00
.task-tab {
background: rgba(255, 255, 255, 0.1);
padding: 10px 20px;
border-radius: 5px;
2025-11-26 13:55:04 +08:00
cursor: pointer;
2025-12-11 13:41:07 +08:00
white-space: nowrap;
transition: all 0.3s ease;
border-bottom: 3px solid transparent;
2025-11-26 13:55:04 +08:00
}
2025-12-11 13:41:07 +08:00
.task-tab.active {
background: rgba(52, 152, 219, 0.2);
border-bottom-color: #3498db;
2025-11-26 13:55:04 +08:00
}
2025-12-11 13:41:07 +08:00
.task-tab:hover {
background: rgba(255, 255, 255, 0.2);
2025-11-26 13:55:04 +08:00
}
</style>
</head>
<body>
<div class="container">
<header>
2025-12-11 13:41:07 +08:00
<h1><i class="fas fa-video"></i> 实时视频流检测播放器</h1>
<p class="subtitle">实时查看视频流检测结果和统计数据</p>
2025-11-26 13:55:04 +08:00
</header>
2025-12-11 13:41:07 +08:00
<div class="task-selector" id="taskTabs">
<!-- 任务选项卡将动态生成 -->
</div>
2025-11-26 13:55:04 +08:00
2025-12-11 13:41:07 +08:00
<div class="player-container">
<div class="video-section">
<div class="video-wrapper">
<video id="videoElement" controls autoplay muted playsinline>
您的浏览器不支持 video 标签。
</video>
2025-11-26 13:55:04 +08:00
</div>
2025-12-11 13:41:07 +08:00
<div class="video-controls">
<button class="control-btn" id="playBtn">
<i class="fas fa-play"></i> 播放
</button>
<div class="status-indicator">
<div class="status-dot" id="streamStatus"></div>
<span id="statusText">等待连接...</span>
</div>
<div class="video-info">
分辨率: <span id="videoResolution">-</span> |
延迟: <span id="videoLatency">-</span>ms
</div>
2025-11-26 13:55:04 +08:00
</div>
</div>
2025-12-11 13:41:07 +08:00
<div class="info-panel">
<div class="info-card">
<h3><i class="fas fa-info-circle"></i> 流信息</h3>
<div class="info-row">
<span class="info-label">当前任务:</span>
<span class="info-value" id="currentTask">未选择</span>
2025-11-26 13:55:04 +08:00
</div>
2025-12-11 13:41:07 +08:00
<div class="info-row">
<span class="info-label">流地址:</span>
<span class="info-value" id="streamUrl">-</span>
2025-11-26 13:55:04 +08:00
</div>
2025-12-11 13:41:07 +08:00
<div class="info-row">
<span class="info-label">FPS:</span>
<span class="info-value" id="streamFps">-</span>
</div>
<div class="info-row">
<span class="info-label">总帧数:</span>
<span class="info-value" id="totalFrames">0</span>
</div>
</div>
2025-11-26 13:55:04 +08:00
2025-12-11 13:41:07 +08:00
<div class="info-card">
<h3><i class="fas fa-chart-bar"></i> 检测统计</h3>
<div class="info-row">
<span class="info-label">检测总数:</span>
<span class="info-value" id="totalDetections">0</span>
</div>
<div class="info-row">
<span class="info-label">当前FPS:</span>
<span class="info-value" id="currentFps">0</span>
</div>
<div class="info-row">
<span class="info-label">平均置信度:</span>
<span class="info-value" id="avgConfidence">0%</span>
</div>
<div class="info-row">
<span class="info-label">最后更新时间:</span>
<span class="info-value" id="lastUpdate">-</span>
</div>
</div>
2025-11-26 13:55:04 +08:00
2025-12-11 13:41:07 +08:00
<div class="info-card detection-list">
<h3><i class="fas fa-search"></i> 实时检测结果</h3>
<div id="detectionsContainer">
<div class="empty-state">
<i class="fas fa-binoculars"></i>
<p>等待检测结果...</p>
</div>
</div>
</div>
</div>
2025-11-26 13:55:04 +08:00
</div>
2025-12-11 13:41:07 +08:00
<div class="stream-controls">
<div class="input-group">
<label for="customStreamUrl"><i class="fas fa-link"></i> 自定义流地址</label>
<input type="text" id="customStreamUrl"
placeholder="例如: rtmp://localhost:1935/live/13">
</div>
<div class="input-group">
<label for="streamType"><i class="fas fa-cog"></i> 流类型</label>
<input type="text" id="streamType" value="flv" disabled>
</div>
<div style="display: flex; align-items: flex-end;">
<button class="control-btn" id="loadCustomStream">
<i class="fas fa-sync-alt"></i> 加载自定义流
</button>
</div>
</div>
2025-11-26 13:55:04 +08:00
</div>
2025-12-11 13:41:07 +08:00
<script src="https://cdn.socket.io/4.6.0/socket.io.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/js/all.min.js"></script>
2025-11-26 13:55:04 +08:00
<script>
// 全局变量
2025-12-11 13:41:07 +08:00
let flvPlayer = null;
let socket = null;
let currentTaskId = null;
let tasks = {};
let detectionStats = {
totalDetections: 0,
confidenceSum: 0,
lastUpdate: null,
frameCount: 0,
lastFrameTime: Date.now(),
currentFps: 0
};
// DOM加载完成后初始化
document.addEventListener('DOMContentLoaded', function() {
initWebSocket();
initEventListeners();
loadTasks();
// 默认加载第一个任务
setTimeout(() => {
if (Object.keys(tasks).length > 0) {
const firstTaskId = Object.keys(tasks)[0];
selectTask(firstTaskId);
2025-11-26 13:55:04 +08:00
}
2025-12-11 13:41:07 +08:00
}, 1000);
});
2025-11-26 13:55:04 +08:00
2025-12-11 13:41:07 +08:00
// 初始化WebSocket连接
function initWebSocket() {
socket = io();
2025-11-26 13:55:04 +08:00
2025-12-11 13:41:07 +08:00
socket.on('connect', function() {
updateStreamStatus(true, 'WebSocket已连接');
console.log('WebSocket连接成功');
});
2025-11-26 13:55:04 +08:00
2025-12-11 13:41:07 +08:00
socket.on('disconnect', function() {
updateStreamStatus(false, 'WebSocket已断开');
console.log('WebSocket连接断开');
});
2025-11-26 13:55:04 +08:00
2025-12-11 13:41:07 +08:00
socket.on('connect_error', function(error) {
updateStreamStatus(false, '连接错误');
console.error('WebSocket连接错误:', error);
});
2025-11-26 13:55:04 +08:00
2025-12-11 13:41:07 +08:00
socket.on('detection_results', function(data) {
handleDetectionResults(data);
});
}
2025-11-26 13:55:04 +08:00
2025-12-11 13:41:07 +08:00
// 初始化事件监听器
function initEventListeners() {
// 播放/暂停按钮
const playBtn = document.getElementById('playBtn');
const videoElement = document.getElementById('videoElement');
2025-11-26 13:55:04 +08:00
2025-12-11 13:41:07 +08:00
playBtn.addEventListener('click', function() {
if (videoElement.paused) {
videoElement.play();
playBtn.innerHTML = '<i class="fas fa-pause"></i> 暂停';
} else {
videoElement.pause();
playBtn.innerHTML = '<i class="fas fa-play"></i> 播放';
2025-11-26 13:55:04 +08:00
}
2025-12-11 13:41:07 +08:00
});
2025-11-26 13:55:04 +08:00
2025-12-11 13:41:07 +08:00
// 自定义流加载
document.getElementById('loadCustomStream').addEventListener('click', function() {
const customUrl = document.getElementById('customStreamUrl').value;
if (customUrl) {
loadFlvStream(customUrl);
2025-11-26 13:55:04 +08:00
}
2025-12-11 13:41:07 +08:00
});
2025-11-26 13:55:04 +08:00
2025-12-11 13:41:07 +08:00
// 视频元素事件
videoElement.addEventListener('loadedmetadata', function() {
updateVideoInfo();
});
2025-11-26 13:55:04 +08:00
2025-12-11 13:41:07 +08:00
videoElement.addEventListener('playing', function() {
updateStreamStatus(true, '正在播放');
});
2025-11-26 13:55:04 +08:00
2025-12-11 13:41:07 +08:00
videoElement.addEventListener('pause', function() {
updateStreamStatus(false, '已暂停');
});
videoElement.addEventListener('error', function() {
updateStreamStatus(false, '播放错误');
console.error('视频播放错误:', videoElement.error);
});
2025-11-26 13:55:04 +08:00
}
2025-12-11 13:41:07 +08:00
// 加载任务列表
async function loadTasks() {
try {
const response = await fetch('/api/tasks');
const result = await response.json();
2025-11-26 13:55:04 +08:00
2025-12-11 13:41:07 +08:00
if (result.status === 'success') {
tasks = {};
const taskTabs = document.getElementById('taskTabs');
taskTabs.innerHTML = '';
2025-11-26 13:55:04 +08:00
2025-12-11 13:41:07 +08:00
// 过滤出运行中的任务
const runningTasks = result.data.tasks.filter(task =>
task.status === 'running' || task.status === 'starting'
);
2025-11-26 13:55:04 +08:00
2025-12-11 13:41:07 +08:00
if (runningTasks.length === 0) {
taskTabs.innerHTML = '<div class="empty-state">暂无运行中的任务</div>';
return;
}
2025-11-26 13:55:04 +08:00
2025-12-11 13:41:07 +08:00
// 创建任务选项卡
runningTasks.forEach(task => {
tasks[task.task_id] = task;
2025-11-26 13:55:04 +08:00
2025-12-11 13:41:07 +08:00
const tab = document.createElement('div');
tab.className = 'task-tab';
tab.textContent = task.config.taskname || task.task_id.substring(0, 8);
tab.dataset.taskId = task.task_id;
2025-11-26 13:55:04 +08:00
2025-12-11 13:41:07 +08:00
tab.addEventListener('click', function() {
selectTask(task.task_id);
});
2025-11-26 13:55:04 +08:00
2025-12-11 13:41:07 +08:00
taskTabs.appendChild(tab);
});
2025-11-26 13:55:04 +08:00
2025-12-11 13:41:07 +08:00
// 激活第一个选项卡
if (runningTasks.length > 0 && !currentTaskId) {
const firstTaskId = runningTasks[0].task_id;
selectTask(firstTaskId);
}
}
} catch (error) {
console.error('加载任务失败:', error);
}
}
// 选择任务
function selectTask(taskId) {
// 更新选项卡状态
document.querySelectorAll('.task-tab').forEach(tab => {
tab.classList.remove('active');
if (tab.dataset.taskId === taskId) {
tab.classList.add('active');
}
2025-11-26 13:55:04 +08:00
});
2025-12-11 13:41:07 +08:00
// 更新当前任务
currentTaskId = taskId;
const task = tasks[taskId];
if (task) {
// 更新任务信息
document.getElementById('currentTask').textContent =
task.config.taskname || taskId.substring(0, 8);
// 获取推流地址(如果启用)
const pushUrl = task.config.push?.url;
if (pushUrl && task.config.push?.enable_push) {
// 加载推流
loadFlvStream(pushUrl);
} else {
// 如果没有推流地址,显示提示
updateStreamStatus(false, '该任务未启用推流');
document.getElementById('videoElement').src = '';
}
// 更新流信息
document.getElementById('streamUrl').textContent =
pushUrl || '未启用推流';
document.getElementById('streamFps').textContent =
task.stats.avg_fps || '-';
document.getElementById('totalFrames').textContent =
task.stats.total_frames || 0;
}
2025-11-26 13:55:04 +08:00
}
2025-12-11 13:41:07 +08:00
// 加载FLV流
function loadFlvStream(url) {
// 停止现有播放器
if (flvPlayer) {
flvPlayer.destroy();
flvPlayer = null;
2025-11-26 13:55:04 +08:00
}
2025-12-11 13:41:07 +08:00
const videoElement = document.getElementById('videoElement');
2025-11-26 13:55:04 +08:00
2025-12-11 13:41:07 +08:00
// 检查浏览器支持
if (!flvjs.isSupported()) {
alert('您的浏览器不支持FLV播放请使用Chrome、Firefox或Edge浏览器');
return;
}
console.log("url----------》",url)
// 创建FLV播放器
flvPlayer = flvjs.createPlayer({
type: 'flv',
url: url,
isLive: true,
hasAudio: false
}, {
enableWorker: true,
lazyLoadMaxDuration: 3 * 60,
seekType: 'range',
reuseRedirectedURL: true
});
2025-11-26 13:55:04 +08:00
2025-12-11 13:41:07 +08:00
flvPlayer.attachMediaElement(videoElement);
flvPlayer.load();
2025-11-26 13:55:04 +08:00
2025-12-11 13:41:07 +08:00
// 播放器事件监听
flvPlayer.on(flvjs.Events.ERROR, function(errorType, errorDetail, errorInfo) {
console.error('FLV播放器错误:', errorType, errorDetail, errorInfo);
updateStreamStatus(false, '流加载失败');
});
2025-11-26 13:55:04 +08:00
2025-12-11 13:41:07 +08:00
flvPlayer.on(flvjs.Events.LOADING_COMPLETE, function() {
console.log('FLV流加载完成');
updateStreamStatus(true, '流加载完成');
});
2025-11-26 13:55:04 +08:00
2025-12-11 13:41:07 +08:00
flvPlayer.on(flvjs.Events.METADATA_ARRIVED, function(metadata) {
console.log('流元数据:', metadata);
updateVideoInfo();
});
2025-11-26 13:55:04 +08:00
2025-12-11 13:41:07 +08:00
// 开始播放
flvPlayer.play().catch(error => {
console.error('播放失败:', error);
updateStreamStatus(false, '播放失败');
2025-11-26 13:55:04 +08:00
});
2025-12-11 13:41:07 +08:00
updateStreamStatus(true, '正在连接...');
2025-11-26 13:55:04 +08:00
}
2025-12-11 13:41:07 +08:00
// 更新视频信息
function updateVideoInfo() {
const videoElement = document.getElementById('videoElement');
const resolution = `${videoElement.videoWidth}x${videoElement.videoHeight}`;
document.getElementById('videoResolution').textContent = resolution;
2025-11-26 13:55:04 +08:00
2025-12-11 13:41:07 +08:00
// 计算延迟(简单估算)
if (videoElement.buffered.length > 0) {
const latency = (videoElement.buffered.end(0) - videoElement.currentTime) * 1000;
document.getElementById('videoLatency').textContent = Math.round(latency);
}
2025-11-26 13:55:04 +08:00
}
2025-12-11 13:41:07 +08:00
// 更新流状态
function updateStreamStatus(connected, message) {
const statusDot = document.getElementById('streamStatus');
const statusText = document.getElementById('statusText');
2025-11-26 13:55:04 +08:00
2025-12-11 13:41:07 +08:00
if (connected) {
statusDot.className = 'status-dot connected';
statusText.textContent = message;
} else {
statusDot.className = 'status-dot';
statusText.textContent = message;
}
}
2025-11-26 13:55:04 +08:00
2025-12-11 13:41:07 +08:00
// 处理检测结果
function handleDetectionResults(data) {
// 只显示当前任务的检测结果
if (data.task_id !== currentTaskId) {
return;
}
2025-11-26 13:55:04 +08:00
2025-12-11 13:41:07 +08:00
// 更新统计信息
updateDetectionStats(data);
2025-11-26 13:55:04 +08:00
2025-12-11 13:41:07 +08:00
// 显示检测结果
displayDetections(data);
2025-11-26 13:55:04 +08:00
2025-12-11 13:41:07 +08:00
// 更新最后更新时间
document.getElementById('lastUpdate').textContent =
new Date().toLocaleTimeString('zh-CN');
2025-11-26 13:55:04 +08:00
}
2025-12-11 13:41:07 +08:00
// 更新检测统计
function updateDetectionStats(data) {
// FPS计算
detectionStats.frameCount++;
const now = Date.now();
const timeDiff = now - detectionStats.lastFrameTime;
2025-11-26 13:55:04 +08:00
2025-12-11 13:41:07 +08:00
if (timeDiff >= 1000) { // 每秒更新一次FPS
detectionStats.currentFps = Math.round(
(detectionStats.frameCount * 1000) / timeDiff
);
detectionStats.frameCount = 0;
detectionStats.lastFrameTime = now;
2025-11-26 13:55:04 +08:00
2025-12-11 13:41:07 +08:00
document.getElementById('currentFps').textContent = detectionStats.currentFps;
2025-11-26 13:55:04 +08:00
}
2025-12-11 13:41:07 +08:00
// 检测统计
if (data.detections && data.detections.length > 0) {
detectionStats.totalDetections += data.detections.length;
2025-11-26 13:55:04 +08:00
2025-12-11 13:41:07 +08:00
const totalConfidence = data.detections.reduce((sum, det) =>
sum + det.confidence, 0
);
detectionStats.confidenceSum += totalConfidence;
2025-11-26 13:55:04 +08:00
2025-12-11 13:41:07 +08:00
const avgConfidence = (detectionStats.confidenceSum / detectionStats.totalDetections) * 100;
document.getElementById('totalDetections').textContent = detectionStats.totalDetections;
document.getElementById('avgConfidence').textContent = avgConfidence.toFixed(1) + '%';
2025-11-26 13:55:04 +08:00
}
2025-12-11 13:41:07 +08:00
}
2025-11-26 13:55:04 +08:00
2025-12-11 13:41:07 +08:00
// 显示检测结果
function displayDetections(data) {
const container = document.getElementById('detectionsContainer');
2025-11-26 13:55:04 +08:00
2025-12-11 13:41:07 +08:00
// 如果当前显示空状态,清空容器
if (container.querySelector('.empty-state')) {
container.innerHTML = '';
}
2025-11-26 13:55:04 +08:00
2025-12-11 13:41:07 +08:00
// 限制显示数量最多显示10个
while (container.children.length >= 10) {
container.removeChild(container.firstChild);
}
2025-11-26 13:55:04 +08:00
2025-12-11 13:41:07 +08:00
if (data.detections && data.detections.length > 0) {
data.detections.forEach(detection => {
const detectionItem = document.createElement('div');
detectionItem.className = 'detection-item';
detectionItem.innerHTML = `
<div class="detection-header">
<span class="detection-class">${detection.class_name}</span>
<span class="detection-confidence">${(detection.confidence * 100).toFixed(1)}%</span>
</div>
<div class="detection-box">
位置: [${detection.box.map(c => c.toFixed(1)).join(', ')}]
</div>
`;
// 添加到顶部(最新结果显示在最上面)
container.insertBefore(detectionItem, container.firstChild);
});
2025-11-26 13:55:04 +08:00
} else {
2025-12-11 13:41:07 +08:00
// 如果没有检测结果,显示空状态(如果容器为空)
if (container.children.length === 0) {
container.innerHTML = `
<div class="empty-state">
<i class="fas fa-binoculars"></i>
<p>等待检测结果...</p>
</div>
`;
}
2025-11-26 13:55:04 +08:00
}
}
2025-12-11 13:41:07 +08:00
// 页面卸载时清理
window.addEventListener('beforeunload', function() {
if (flvPlayer) {
flvPlayer.destroy();
flvPlayer = null;
}
2025-11-26 13:55:04 +08:00
2025-12-11 13:41:07 +08:00
if (socket) {
socket.disconnect();
}
});
// 定时刷新任务列表每30秒
setInterval(loadTasks, 30000);
2025-11-26 13:55:04 +08:00
</script>
</body>
</html>