Yolov/templates/demo/video-player.html

816 lines
37 KiB
HTML
Raw Blame History

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.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>