816 lines
37 KiB
HTML
816 lines
37 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>视频播放 - 多任务YOLO检测系统</title>
|
||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
|
||
<link href="css/style.css" rel="stylesheet">
|
||
</head>
|
||
<body>
|
||
<!-- 导航栏 -->
|
||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||
<div class="container-fluid">
|
||
<a class="navbar-brand" href="index.html">
|
||
<i class="bi bi-cpu-fill me-2"></i>多任务YOLO检测系统
|
||
</a>
|
||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||
<span class="navbar-toggler-icon"></span>
|
||
</button>
|
||
<div class="collapse navbar-collapse" id="navbarNav">
|
||
<ul class="navbar-nav ms-auto">
|
||
<li class="nav-item">
|
||
<a class="nav-link" href="index.html">
|
||
<i class="bi bi-house-door me-1"></i>首页
|
||
</a>
|
||
</li>
|
||
<li class="nav-item">
|
||
<a class="nav-link" href="task-management.html">
|
||
<i class="bi bi-list-task me-1"></i>任务管理
|
||
</a>
|
||
</li>
|
||
<li class="nav-item">
|
||
<a class="nav-link" href="monitoring.html">
|
||
<i class="bi bi-graph-up me-1"></i>系统监控
|
||
</a>
|
||
</li>
|
||
<li class="nav-item">
|
||
<a class="nav-link active" href="video-player.html">
|
||
<i class="bi bi-camera-video me-1"></i>视频播放
|
||
</a>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</nav>
|
||
|
||
<!-- 主内容区 -->
|
||
<div class="container-fluid mt-4">
|
||
<div class="row">
|
||
<div class="col-md-12">
|
||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||
<h2><i class="bi bi-camera-video text-primary me-2"></i>视频播放</h2>
|
||
<div>
|
||
<button class="btn btn-outline-primary me-2" id="refreshStreams">
|
||
<i class="bi bi-arrow-clockwise me-1"></i>刷新流列表
|
||
</button>
|
||
<div class="btn-group">
|
||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addStreamModal">
|
||
<i class="bi bi-plus-circle me-1"></i>添加视频流
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 视频播放区域 -->
|
||
<div class="row">
|
||
<div class="col-lg-8 mb-4">
|
||
<div class="card border-0 shadow-sm h-100">
|
||
<div class="card-header bg-white d-flex justify-content-between align-items-center">
|
||
<h5 class="mb-0" id="currentStreamTitle">交通监控-路口A</h5>
|
||
<div class="btn-group">
|
||
<button class="btn btn-sm btn-outline-secondary" id="fullscreenBtn">
|
||
<i class="bi bi-arrows-fullscreen"></i>
|
||
</button>
|
||
<button class="btn btn-sm btn-outline-secondary" id="screenshotBtn">
|
||
<i class="bi bi-camera"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="card-body p-0 position-relative" id="videoContainer">
|
||
<!-- 视频占位图 -->
|
||
<div class="video-placeholder" id="videoPlaceholder">
|
||
<div class="placeholder-content">
|
||
<i class="bi bi-play-circle" style="font-size: 4rem;"></i>
|
||
<p class="mt-3">选择左侧的视频流开始播放</p>
|
||
<p class="small text-muted">支持RTMP/RTSP/HTTP-FLV流</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 模拟视频 -->
|
||
<div class="simulated-video" id="simulatedVideo" style="display: none;">
|
||
<div class="video-header">
|
||
<span class="badge bg-danger me-2">LIVE</span>
|
||
<span class="text-light">1080p • 24 FPS</span>
|
||
</div>
|
||
|
||
<!-- 模拟检测框 -->
|
||
<div class="detection-box" style="top: 30%; left: 40%; width: 120px; height: 200px;">
|
||
<div class="box-label">人 0.92</div>
|
||
</div>
|
||
<div class="detection-box" style="top: 35%; left: 60%; width: 180px; height: 90px;">
|
||
<div class="box-label">汽车 0.87</div>
|
||
</div>
|
||
<div class="detection-box" style="top: 60%; left: 20%; width: 100px; height: 150px;">
|
||
<div class="box-label">自行车 0.78</div>
|
||
</div>
|
||
<div class="detection-box" style="top: 50%; left: 70%; width: 140px; height: 80px;">
|
||
<div class="box-label">卡车 0.81</div>
|
||
</div>
|
||
|
||
<!-- 视频信息 -->
|
||
<div class="video-info">
|
||
<div class="info-item">
|
||
<i class="bi bi-clock"></i>
|
||
<span>12:45:23</span>
|
||
</div>
|
||
<div class="info-item">
|
||
<i class="bi bi-geo-alt"></i>
|
||
<span>路口A</span>
|
||
</div>
|
||
<div class="info-item">
|
||
<i class="bi bi-cpu"></i>
|
||
<span>模型: yolov8n, yolov8s</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- FPS显示 -->
|
||
<div class="fps-display">
|
||
<span>FPS: <strong>24.5</strong></span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 播放控制 -->
|
||
<div class="video-controls" id="videoControls" style="display: none;">
|
||
<div class="controls-bg"></div>
|
||
<div class="controls-content">
|
||
<div class="d-flex justify-content-between align-items-center">
|
||
<div>
|
||
<button class="btn btn-sm btn-light me-2" id="playPauseBtn">
|
||
<i class="bi bi-pause"></i>
|
||
</button>
|
||
<button class="btn btn-sm btn-light" id="volumeBtn">
|
||
<i class="bi bi-volume-up"></i>
|
||
</button>
|
||
</div>
|
||
<div class="flex-grow-1 mx-3">
|
||
<div class="progress" style="height: 4px;">
|
||
<div class="progress-bar" style="width: 45%"></div>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<span class="text-light me-3">12:45 / 30:00</span>
|
||
<button class="btn btn-sm btn-light" id="settingsBtn">
|
||
<i class="bi bi-gear"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="card-footer bg-white">
|
||
<div class="row">
|
||
<div class="col-md-6">
|
||
<h6>检测统计</h6>
|
||
<div class="d-flex flex-wrap gap-2">
|
||
<span class="badge bg-primary">人: 3</span>
|
||
<span class="badge bg-success">汽车: 2</span>
|
||
<span class="badge bg-warning">自行车: 1</span>
|
||
<span class="badge bg-danger">卡车: 1</span>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-6 text-md-end">
|
||
<h6>流信息</h6>
|
||
<p class="mb-0 small">
|
||
<span class="text-muted">URL:</span>
|
||
<code id="currentStreamUrl">rtmp://localhost:1935/live/camera1</code>
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="col-lg-4 mb-4">
|
||
<!-- 流列表 -->
|
||
<div class="card border-0 shadow-sm h-100">
|
||
<div class="card-header bg-white">
|
||
<h5 class="mb-0">可用视频流</h5>
|
||
</div>
|
||
<div class="card-body p-0">
|
||
<div class="list-group list-group-flush" id="streamList">
|
||
<!-- 流列表项将通过JavaScript动态生成 -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 检测详情 -->
|
||
<div class="card border-0 shadow-sm mt-4">
|
||
<div class="card-header bg-white">
|
||
<h5 class="mb-0">实时检测详情</h5>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="table-responsive">
|
||
<table class="table table-sm" style="height:300px">
|
||
<thead>
|
||
<tr>
|
||
<th>类型</th>
|
||
<th>置信度</th>
|
||
<th>位置</th>
|
||
<th>模型</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="detectionDetails">
|
||
<!-- 检测详情将通过JavaScript动态生成 -->
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<div class="text-center mt-3">
|
||
<div class="spinner-border spinner-border-sm text-primary" role="status" id="detectionSpinner">
|
||
<span class="visually-hidden">检测中...</span>
|
||
</div>
|
||
<small class="text-muted ms-2">实时检测中...</small>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 历史记录 -->
|
||
<div class="card border-0 shadow-sm mt-4 col-lg-8">
|
||
<div class="card-header bg-white">
|
||
<h5 class="mb-0">视频录制历史</h5>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="row" id="recordingHistory">
|
||
<!-- 录制历史将通过JavaScript动态生成 -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 添加视频流模态框 -->
|
||
<div class="modal fade" id="addStreamModal" tabindex="-1">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title">添加视频流</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<form id="addStreamForm">
|
||
<div class="mb-3">
|
||
<label for="streamName" class="form-label">流名称</label>
|
||
<input type="text" class="form-control" id="streamName" placeholder="例如:交通监控-路口A" required>
|
||
</div>
|
||
<div class="mb-3">
|
||
<label for="streamUrl" class="form-label">流地址</label>
|
||
<input type="text" class="form-control" id="streamUrl" placeholder="例如:rtmp://example.com/live/stream" required>
|
||
</div>
|
||
<div class="mb-3">
|
||
<label for="streamType" class="form-label">流类型</label>
|
||
<select class="form-select" id="streamType">
|
||
<option value="rtmp">RTMP</option>
|
||
<option value="rtsp">RTSP</option>
|
||
<option value="http-flv">HTTP-FLV</option>
|
||
<option value="hls">HLS</option>
|
||
<option value="webrtc">WebRTC</option>
|
||
</select>
|
||
</div>
|
||
<div class="mb-3">
|
||
<div class="form-check">
|
||
<input class="form-check-input" type="checkbox" id="enableDetection" checked>
|
||
<label class="form-check-label" for="enableDetection">
|
||
启用AI检测
|
||
</label>
|
||
</div>
|
||
</div>
|
||
<div class="mb-3" id="modelSelection" style="display: none;">
|
||
<label class="form-label">选择检测模型</label>
|
||
<div class="border rounded p-2">
|
||
<div class="form-check">
|
||
<input class="form-check-input" type="checkbox" id="detectPerson" checked>
|
||
<label class="form-check-label" for="detectPerson">
|
||
行人检测
|
||
</label>
|
||
</div>
|
||
<div class="form-check">
|
||
<input class="form-check-input" type="checkbox" id="detectVehicle" checked>
|
||
<label class="form-check-label" for="detectVehicle">
|
||
车辆检测
|
||
</label>
|
||
</div>
|
||
<div class="form-check">
|
||
<input class="form-check-input" type="checkbox" id="detectBicycle">
|
||
<label class="form-check-label" for="detectBicycle">
|
||
自行车检测
|
||
</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
|
||
<button type="button" class="btn btn-primary" id="submitStreamForm">添加流</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 页脚 -->
|
||
<footer class="bg-dark text-white mt-5 py-4">
|
||
<div class="container-fluid">
|
||
<div class="row">
|
||
<div class="col-md-6">
|
||
<h5>视频播放</h5>
|
||
<p class="text-light">多任务YOLO检测系统的视频播放界面</p>
|
||
</div>
|
||
<div class="col-md-6 text-md-end">
|
||
<p class="mb-0">© 2023 多任务YOLO检测系统 | 演示版本 v1.0.0</p>
|
||
<p class="text-light small">此页面为演示版本,使用模拟数据</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</footer>
|
||
|
||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||
<script>
|
||
// 模拟视频流数据
|
||
const mockStreams = [
|
||
{
|
||
id: 1,
|
||
name: "交通监控-路口A",
|
||
url: "rtmp://localhost:1935/live/camera1",
|
||
type: "rtmp",
|
||
status: "online",
|
||
fps: 24.5,
|
||
resolution: "1920x1080",
|
||
detectionEnabled: true,
|
||
models: ["yolov8n.pt", "yolov8s.pt"],
|
||
lastActive: "2分钟前"
|
||
},
|
||
{
|
||
id: 2,
|
||
name: "安全监控-入口",
|
||
url: "rtmp://localhost:1935/live/camera2",
|
||
type: "rtmp",
|
||
status: "online",
|
||
fps: 28.1,
|
||
resolution: "1280x720",
|
||
detectionEnabled: true,
|
||
models: ["yolov8n.pt"],
|
||
lastActive: "5分钟前"
|
||
},
|
||
{
|
||
id: 3,
|
||
name: "停车场监控",
|
||
url: "rtsp://192.168.1.100:554/live",
|
||
type: "rtsp",
|
||
status: "offline",
|
||
fps: 0,
|
||
resolution: "1920x1080",
|
||
detectionEnabled: false,
|
||
models: [],
|
||
lastActive: "1小时前"
|
||
},
|
||
{
|
||
id: 4,
|
||
name: "生产线检测",
|
||
url: "http://192.168.1.101:8000/live.flv",
|
||
type: "http-flv",
|
||
status: "online",
|
||
fps: 15.3,
|
||
resolution: "1280x720",
|
||
detectionEnabled: true,
|
||
models: ["custom.pt"],
|
||
lastActive: "10分钟前"
|
||
},
|
||
{
|
||
id: 5,
|
||
name: "测试流",
|
||
url: "rtmp://test.com:1935/live/test",
|
||
type: "rtmp",
|
||
status: "online",
|
||
fps: 30.0,
|
||
resolution: "1920x1080",
|
||
detectionEnabled: false,
|
||
models: [],
|
||
lastActive: "刚刚"
|
||
}
|
||
];
|
||
|
||
// 模拟检测数据
|
||
const mockDetections = [
|
||
{ type: "人", confidence: 0.92, position: "x:320, y:240", model: "yolov8n.pt" },
|
||
{ type: "汽车", confidence: 0.87, position: "x:480, y:280", model: "yolov8s.pt" },
|
||
{ type: "自行车", confidence: 0.78, position: "x:160, y:360", model: "yolov8n.pt" },
|
||
{ type: "卡车", confidence: 0.81, position: "x:560, y:320", model: "yolov8s.pt" },
|
||
{ type: "人", confidence: 0.88, position: "x:400, y:200", model: "yolov8n.pt" }
|
||
];
|
||
|
||
// 模拟录制历史
|
||
const mockRecordings = [
|
||
{ id: 1, name: "交通监控-20231015-0930.mp4", duration: "5分30秒", size: "125MB", date: "2023-10-15 09:30", stream: "交通监控-路口A" },
|
||
{ id: 2, name: "安全监控-20231015-1015.mp4", duration: "3分15秒", size: "78MB", date: "2023-10-15 10:15", stream: "安全监控-入口" },
|
||
{ id: 3, name: "生产线-20231015-0845.mp4", duration: "10分45秒", size: "245MB", date: "2023-10-15 08:45", stream: "生产线检测" },
|
||
];
|
||
|
||
// 当前选中的流
|
||
let currentStream = null;
|
||
let isPlaying = false;
|
||
let detectionInterval = null;
|
||
|
||
// 渲染流列表
|
||
function renderStreamList() {
|
||
const list = document.getElementById('streamList');
|
||
list.innerHTML = '';
|
||
|
||
mockStreams.forEach(stream => {
|
||
const item = document.createElement('a');
|
||
item.href = '#';
|
||
item.className = 'list-group-item list-group-item-action';
|
||
if (currentStream && currentStream.id === stream.id) {
|
||
item.classList.add('active');
|
||
}
|
||
|
||
const statusColor = stream.status === 'online' ? 'success' : 'secondary';
|
||
const statusText = stream.status === 'online' ? '在线' : '离线';
|
||
|
||
item.innerHTML = `
|
||
<div class="d-flex justify-content-between align-items-center">
|
||
<div>
|
||
<h6 class="mb-1">${stream.name}</h6>
|
||
<p class="mb-1 small text-muted">
|
||
<code>${stream.url}</code>
|
||
</p>
|
||
<small>
|
||
<span class="badge bg-${statusColor} me-2">${statusText}</span>
|
||
${stream.detectionEnabled ? '<span class="badge bg-primary me-2">AI检测</span>' : ''}
|
||
<span class="text-muted">${stream.resolution}</span>
|
||
</small>
|
||
</div>
|
||
<div class="text-end">
|
||
<div class="mb-1">
|
||
<span class="badge bg-info">${stream.fps > 0 ? stream.fps.toFixed(1) + ' FPS' : '离线'}</span>
|
||
</div>
|
||
<small class="text-muted">${stream.lastActive}</small>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
item.addEventListener('click', function(e) {
|
||
e.preventDefault();
|
||
selectStream(stream);
|
||
});
|
||
|
||
list.appendChild(item);
|
||
});
|
||
}
|
||
|
||
// 渲染检测详情
|
||
function renderDetectionDetails() {
|
||
const tbody = document.getElementById('detectionDetails');
|
||
tbody.innerHTML = '';
|
||
|
||
mockDetections.forEach(detection => {
|
||
const row = document.createElement('tr');
|
||
|
||
// 置信度颜色
|
||
let confidenceColor = 'success';
|
||
if (detection.confidence < 0.7) {
|
||
confidenceColor = 'warning';
|
||
} else if (detection.confidence < 0.5) {
|
||
confidenceColor = 'danger';
|
||
}
|
||
|
||
row.innerHTML = `
|
||
<td>
|
||
<span class="badge bg-primary">${detection.type}</span>
|
||
</td>
|
||
<td>
|
||
<span class="badge bg-${confidenceColor}">${(detection.confidence * 100).toFixed(1)}%</span>
|
||
</td>
|
||
<td><small>${detection.position}</small></td>
|
||
<td><small>${detection.model}</small></td>
|
||
`;
|
||
|
||
tbody.appendChild(row);
|
||
});
|
||
}
|
||
|
||
// 渲染录制历史
|
||
function renderRecordingHistory() {
|
||
const container = document.getElementById('recordingHistory');
|
||
container.innerHTML = '';
|
||
|
||
mockRecordings.forEach(recording => {
|
||
const col = document.createElement('div');
|
||
col.className = 'col-md-3 mb-3';
|
||
|
||
col.innerHTML = `
|
||
<div class="card h-100">
|
||
<div class="card-body">
|
||
<h6 class="card-title small">${recording.name}</h6>
|
||
<p class="card-text small text-muted">
|
||
<i class="bi bi-clock me-1"></i>${recording.duration}<br>
|
||
<i class="bi bi-hdd me-1"></i>${recording.size}<br>
|
||
<i class="bi bi-camera-video me-1"></i>${recording.stream}
|
||
</p>
|
||
</div>
|
||
<div class="card-footer bg-white">
|
||
<div class="d-flex justify-content-between align-items-center">
|
||
<small class="text-muted">${recording.date}</small>
|
||
<div>
|
||
<button class="btn btn-sm btn-outline-primary me-1">
|
||
<i class="bi bi-play"></i>
|
||
</button>
|
||
<button class="btn btn-sm btn-outline-danger">
|
||
<i class="bi bi-download"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
container.appendChild(col);
|
||
});
|
||
}
|
||
|
||
// 选择视频流
|
||
function selectStream(stream) {
|
||
currentStream = stream;
|
||
|
||
// 更新UI
|
||
document.getElementById('currentStreamTitle').textContent = stream.name;
|
||
document.getElementById('currentStreamUrl').textContent = stream.url;
|
||
|
||
// 显示模拟视频
|
||
document.getElementById('videoPlaceholder').style.display = 'none';
|
||
document.getElementById('simulatedVideo').style.display = 'block';
|
||
document.getElementById('videoControls').style.display = 'block';
|
||
|
||
// 开始模拟播放
|
||
if (!isPlaying) {
|
||
togglePlayPause();
|
||
}
|
||
|
||
// 更新流列表活动状态
|
||
renderStreamList();
|
||
|
||
// 开始模拟检测更新
|
||
startDetectionSimulation();
|
||
|
||
// 显示通知
|
||
showNotification('视频流已切换', `已切换到 ${stream.name}`, 'success');
|
||
}
|
||
|
||
// 开始模拟检测
|
||
function startDetectionSimulation() {
|
||
// 清除之前的定时器
|
||
if (detectionInterval) {
|
||
clearInterval(detectionInterval);
|
||
}
|
||
|
||
// 显示加载动画
|
||
const spinner = document.getElementById('detectionSpinner');
|
||
spinner.style.display = 'inline-block';
|
||
|
||
// 模拟检测更新
|
||
detectionInterval = setInterval(() => {
|
||
// 随机更新检测数据
|
||
mockDetections.forEach(detection => {
|
||
// 随机微调置信度
|
||
const change = (Math.random() - 0.5) * 0.1;
|
||
detection.confidence = Math.max(0.3, Math.min(0.99, detection.confidence + change));
|
||
|
||
// 随机微调位置
|
||
if (Math.random() > 0.7) {
|
||
const x = Math.floor(Math.random() * 640);
|
||
const y = Math.floor(Math.random() * 480);
|
||
detection.position = `x:${x}, y:${y}`;
|
||
}
|
||
});
|
||
|
||
// 随机添加或删除检测
|
||
if (Math.random() > 0.8 && mockDetections.length < 8) {
|
||
// 添加新检测
|
||
const types = ["人", "汽车", "自行车", "卡车", "摩托车", "公交车"];
|
||
const models = ["yolov8n.pt", "yolov8s.pt", "yolov8m.pt"];
|
||
const newType = types[Math.floor(Math.random() * types.length)];
|
||
const newModel = models[Math.floor(Math.random() * models.length)];
|
||
|
||
mockDetections.push({
|
||
type: newType,
|
||
confidence: Math.random() * 0.3 + 0.6,
|
||
position: `x:${Math.floor(Math.random() * 640)}, y:${Math.floor(Math.random() * 480)}`,
|
||
model: newModel
|
||
});
|
||
} else if (Math.random() > 0.9 && mockDetections.length > 3) {
|
||
// 移除检测
|
||
mockDetections.splice(Math.floor(Math.random() * mockDetections.length), 1);
|
||
}
|
||
|
||
// 更新检测详情表格
|
||
renderDetectionDetails();
|
||
|
||
// 更新检测统计
|
||
updateDetectionStats();
|
||
|
||
}, 2000);
|
||
}
|
||
|
||
// 更新检测统计
|
||
function updateDetectionStats() {
|
||
// 统计各类检测数量
|
||
const counts = {};
|
||
mockDetections.forEach(detection => {
|
||
counts[detection.type] = (counts[detection.type] || 0) + 1;
|
||
});
|
||
|
||
// 这里可以更新检测统计的显示
|
||
// 在实际实现中,可以更新页面上的统计信息
|
||
}
|
||
|
||
// 切换播放/暂停
|
||
function togglePlayPause() {
|
||
const button = document.getElementById('playPauseBtn');
|
||
const icon = button.querySelector('i');
|
||
|
||
if (isPlaying) {
|
||
// 暂停
|
||
icon.className = 'bi bi-play';
|
||
button.setAttribute('title', '播放');
|
||
isPlaying = false;
|
||
|
||
// 暂停检测更新
|
||
if (detectionInterval) {
|
||
clearInterval(detectionInterval);
|
||
detectionInterval = null;
|
||
}
|
||
|
||
// 隐藏加载动画
|
||
document.getElementById('detectionSpinner').style.display = 'none';
|
||
} else {
|
||
// 播放
|
||
icon.className = 'bi bi-pause';
|
||
button.setAttribute('title', '暂停');
|
||
isPlaying = true;
|
||
|
||
// 开始检测更新
|
||
startDetectionSimulation();
|
||
}
|
||
}
|
||
|
||
// 切换全屏
|
||
function toggleFullscreen() {
|
||
const container = document.getElementById('videoContainer');
|
||
|
||
if (!document.fullscreenElement) {
|
||
if (container.requestFullscreen) {
|
||
container.requestFullscreen();
|
||
} else if (container.webkitRequestFullscreen) {
|
||
container.webkitRequestFullscreen();
|
||
} else if (container.msRequestFullscreen) {
|
||
container.msRequestFullscreen();
|
||
}
|
||
|
||
document.getElementById('fullscreenBtn').innerHTML = '<i class="bi bi-fullscreen-exit"></i>';
|
||
showNotification('全屏模式', '已进入全屏模式', 'info');
|
||
} else {
|
||
if (document.exitFullscreen) {
|
||
document.exitFullscreen();
|
||
} else if (document.webkitExitFullscreen) {
|
||
document.webkitExitFullscreen();
|
||
} else if (document.msExitFullscreen) {
|
||
document.msExitFullscreen();
|
||
}
|
||
|
||
document.getElementById('fullscreenBtn').innerHTML = '<i class="bi bi-arrows-fullscreen"></i>';
|
||
showNotification('退出全屏', '已退出全屏模式', 'info');
|
||
}
|
||
}
|
||
|
||
// 截图
|
||
function takeScreenshot() {
|
||
// 模拟截图
|
||
showNotification('截图已保存', '视频截图已保存到下载文件夹', 'success');
|
||
|
||
// 在实际实现中,这里可以调用截图功能
|
||
}
|
||
|
||
// 添加新视频流
|
||
function addNewStream() {
|
||
const name = document.getElementById('streamName').value;
|
||
const url = document.getElementById('streamUrl').value;
|
||
const type = document.getElementById('streamType').value;
|
||
const enableDetection = document.getElementById('enableDetection').checked;
|
||
|
||
if (!name || !url) {
|
||
showNotification('表单验证失败', '请填写流名称和地址', 'danger');
|
||
return;
|
||
}
|
||
|
||
// 创建新流
|
||
const newStream = {
|
||
id: mockStreams.length + 1,
|
||
name: name,
|
||
url: url,
|
||
type: type,
|
||
status: "online",
|
||
fps: Math.floor(Math.random() * 10) + 20,
|
||
resolution: "1920x1080",
|
||
detectionEnabled: enableDetection,
|
||
models: enableDetection ? ["yolov8n.pt"] : [],
|
||
lastActive: "刚刚"
|
||
};
|
||
|
||
mockStreams.push(newStream);
|
||
|
||
// 关闭模态框
|
||
bootstrap.Modal.getInstance(document.getElementById('addStreamModal')).hide();
|
||
|
||
// 重置表单
|
||
document.getElementById('addStreamForm').reset();
|
||
|
||
// 更新流列表
|
||
renderStreamList();
|
||
|
||
// 选择新添加的流
|
||
selectStream(newStream);
|
||
|
||
showNotification('流添加成功', `已添加视频流: ${name}`, 'success');
|
||
}
|
||
|
||
// 刷新流列表
|
||
function refreshStreams() {
|
||
// 模拟更新流状态
|
||
mockStreams.forEach(stream => {
|
||
// 随机改变一些流的状态
|
||
if (Math.random() > 0.7) {
|
||
stream.status = stream.status === 'online' ? 'offline' : 'online';
|
||
stream.lastActive = stream.status === 'online' ? '刚刚' : '1分钟前';
|
||
}
|
||
|
||
// 更新FPS
|
||
if (stream.status === 'online') {
|
||
stream.fps = (Math.random() * 10 + 20).toFixed(1);
|
||
} else {
|
||
stream.fps = 0;
|
||
}
|
||
});
|
||
|
||
// 重新渲染流列表
|
||
renderStreamList();
|
||
|
||
showNotification('流列表已刷新', '视频流状态已更新', 'info');
|
||
}
|
||
|
||
// 显示通知
|
||
function showNotification(title, message, type) {
|
||
// 创建通知元素
|
||
const notification = document.createElement('div');
|
||
notification.className = `alert alert-${type} alert-dismissible fade show position-fixed`;
|
||
notification.style.cssText = 'top: 20px; right: 20px; z-index: 1050; min-width: 300px;';
|
||
notification.innerHTML = `
|
||
<strong>${title}</strong> ${message}
|
||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||
`;
|
||
|
||
document.body.appendChild(notification);
|
||
|
||
// 3秒后自动移除
|
||
setTimeout(() => {
|
||
if (notification.parentNode) {
|
||
notification.remove();
|
||
}
|
||
}, 3000);
|
||
}
|
||
|
||
// 初始化
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
// 渲染初始数据
|
||
renderStreamList();
|
||
renderDetectionDetails();
|
||
renderRecordingHistory();
|
||
|
||
// 默认选择第一个流
|
||
if (mockStreams.length > 0) {
|
||
selectStream(mockStreams[0]);
|
||
}
|
||
|
||
// 绑定事件
|
||
document.getElementById('playPauseBtn').addEventListener('click', togglePlayPause);
|
||
document.getElementById('fullscreenBtn').addEventListener('click', toggleFullscreen);
|
||
document.getElementById('screenshotBtn').addEventListener('click', takeScreenshot);
|
||
document.getElementById('submitStreamForm').addEventListener('click', addNewStream);
|
||
document.getElementById('refreshStreams').addEventListener('click', refreshStreams);
|
||
|
||
// 检测启用复选框事件
|
||
document.getElementById('enableDetection').addEventListener('change', function() {
|
||
const modelSelection = document.getElementById('modelSelection');
|
||
modelSelection.style.display = this.checked ? 'block' : 'none';
|
||
});
|
||
|
||
// 显示欢迎消息
|
||
setTimeout(() => {
|
||
showNotification('视频播放器已就绪', '选择左侧的视频流开始播放', 'success');
|
||
}, 1000);
|
||
});
|
||
</script>
|
||
</body>
|
||
</html> |