Yolov/templates/demo/video_player.html

841 lines
29 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>智能监控视频系统</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
}
body {
background-color: #0d1117;
color: #e6edf3;
width: 1920px;
height: 1080px;
overflow: hidden;
display: flex;
}
/* 侧边栏样式 */
.sidebar {
width: 320px;
background-color: #161b22;
border-right: 1px solid #30363d;
display: flex;
flex-direction: column;
overflow: hidden;
}
.header {
padding: 24px 20px;
border-bottom: 1px solid #30363d;
}
.logo {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 20px;
}
.logo i {
font-size: 28px;
color: #58a6ff;
}
.logo h1 {
font-size: 22px;
font-weight: 600;
}
.status-info {
display: flex;
justify-content: space-between;
margin-top: 16px;
font-size: 14px;
}
.online {
color: #3fb950;
}
.offline {
color: #f85149;
}
.camera-list {
flex: 1;
overflow-y: auto;
padding: 16px 0;
}
.camera-list::-webkit-scrollbar {
width: 6px;
}
.camera-list::-webkit-scrollbar-thumb {
background-color: #30363d;
border-radius: 3px;
}
.camera-item {
padding: 16px 20px;
border-bottom: 1px solid #21262d;
cursor: pointer;
transition: all 0.2s;
display: flex;
align-items: center;
gap: 12px;
}
.camera-item:hover {
background-color: #1c2128;
}
.camera-item.active {
background-color: #1c2128;
border-left: 3px solid #58a6ff;
}
.camera-icon {
width: 40px;
height: 40px;
border-radius: 8px;
background-color: #238636;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
}
.camera-info {
flex: 1;
}
.camera-name {
font-weight: 600;
margin-bottom: 4px;
}
.camera-location {
font-size: 13px;
color: #8b949e;
}
.camera-status {
width: 10px;
height: 10px;
border-radius: 50%;
}
.camera-status.online {
background-color: #3fb950;
}
.camera-status.offline {
background-color: #f85149;
}
.camera-status.recording {
background-color: #f0883e;
animation: pulse 1.5s infinite;
}
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.5; }
100% { opacity: 1; }
}
/* 主内容区样式 */
.main-content {
flex: 1;
display: flex;
flex-direction: column;
padding: 24px;
overflow: hidden;
}
.controls {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
padding-bottom: 20px;
border-bottom: 1px solid #30363d;
}
.layout-controls {
display: flex;
gap: 12px;
}
.layout-btn {
padding: 10px 20px;
background-color: #21262d;
border: 1px solid #30363d;
color: #c9d1d9;
border-radius: 6px;
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
transition: all 0.2s;
font-weight: 500;
}
.layout-btn:hover {
background-color: #30363d;
border-color: #8b949e;
}
.layout-btn.active {
background-color: #1f6feb;
border-color: #1f6feb;
color: white;
}
.time-display {
font-size: 16px;
color: #8b949e;
display: flex;
align-items: center;
gap: 8px;
}
/* 视频网格样式 */
.video-grid {
flex: 1;
display: grid;
gap: 16px;
transition: all 0.3s;
}
.video-grid.single {
grid-template-columns: 1fr;
grid-template-rows: 1fr;
}
.video-grid.four {
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr 1fr;
}
.video-grid.nine {
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: 1fr 1fr 1fr;
}
.video-player {
background-color: #000;
border-radius: 8px;
overflow: hidden;
position: relative;
border: 1px solid #30363d;
display: flex;
flex-direction: column;
}
.video-header {
padding: 12px 16px;
background-color: rgba(0, 0, 0, 0.7);
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #30363d;
}
.video-title {
font-weight: 600;
font-size: 14px;
display: flex;
align-items: center;
gap: 8px;
}
.video-controls {
display: flex;
gap: 12px;
}
.control-btn {
background: none;
border: none;
color: #c9d1d9;
cursor: pointer;
font-size: 14px;
padding: 4px;
border-radius: 4px;
transition: all 0.2s;
}
.control-btn:hover {
background-color: rgba(255, 255, 255, 0.1);
}
.video-container {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
position: relative;
}
.video-feed {
width: 100%;
height: 100%;
object-fit: cover;
}
.no-video {
color: #8b949e;
font-size: 16px;
text-align: center;
}
.playing-indicator {
position: absolute;
top: 10px;
right: 10px;
background-color: rgba(0, 0, 0, 0.7);
color: #f85149;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
display: flex;
align-items: center;
gap: 4px;
}
/* 时间更新函数 */
.time-update {
margin-top: 20px;
text-align: center;
color: #8b949e;
font-size: 14px;
}
/* 响应式调整 */
@media (max-width: 1920px) {
body {
width: 100%;
height: 100vh;
}
}
</style>
</head>
<body>
<!-- 左侧边栏 -->
<div class="sidebar">
<div class="header">
<div class="logo">
<i class="fas fa-shield-alt"></i>
<h1>智能监控系统</h1>
</div>
<p>监控中心 v2.1</p>
<div class="status-info">
<div class="online">
<i class="fas fa-circle"></i> 在线: <span id="online-count">8</span>
</div>
<div class="offline">
<i class="fas fa-circle"></i> 离线: <span id="offline-count">2</span>
</div>
</div>
</div>
<div class="camera-list" id="camera-list">
<!-- 摄像头列表将通过JavaScript动态生成 -->
</div>
</div>
<!-- 主内容区 -->
<div class="main-content">
<div class="controls">
<div class="layout-controls">
<button class="layout-btn active" data-layout="single">
<i class="fas fa-expand"></i> 单画面
</button>
<button class="layout-btn" data-layout="four">
<i class="fas fa-th-large"></i> 四画面
</button>
<button class="layout-btn" data-layout="nine">
<i class="fas fa-th"></i> 九画面
</button>
</div>
<div class="time-display">
<i class="far fa-clock"></i>
<span id="current-time">--:--:--</span>
</div>
</div>
<div class="video-grid nine" id="video-grid">
<!-- 视频播放器将通过JavaScript动态生成 -->
</div>
<div class="time-update">
<p>最后更新: <span id="last-update">--:--:--</span> | 系统运行正常</p>
</div>
</div>
<script>
// 模拟摄像头数据
const cameras = [
{ id: 1, name: "主入口", location: "一楼大厅", status: "online", recording: true, feed: "https://assets.mixkit.co/videos/preview/mixkit-security-camera-on-the-corner-of-a-building-42939-large.mp4" },
{ id: 2, name: "停车场", location: "地下B1层", status: "online", recording: true, feed: "https://assets.mixkit.co/videos/preview/mixkit-parking-lot-entrance-from-a-high-angle-42942-large.mp4" },
{ id: 3, name: "电梯间", location: "1号电梯", status: "online", recording: false, feed: "https://assets.mixkit.co/videos/preview/mixkit-going-up-the-stairs-of-a-modern-building-42944-large.mp4" },
{ id: 4, name: "财务室", location: "三楼301", status: "online", recording: true, feed: "https://assets.mixkit.co/videos/preview/mixkit-woman-working-on-a-laptop-42945-large.mp4" },
{ id: 5, name: "仓库", location: "地下一层", status: "offline", recording: false, feed: "" },
{ id: 6, name: "侧门", location: "建筑东侧", status: "online", recording: false, feed: "https://assets.mixkit.co/videos/preview/mixkit-exit-sign-on-a-building-wall-42943-large.mp4" },
{ id: 7, name: "办公室", location: "二楼201", status: "online", recording: false, feed: "https://assets.mixkit.co/videos/preview/mixkit-open-plan-office-with-modern-design-42941-large.mp4" },
{ id: 8, name: "走廊", location: "三楼北侧", status: "online", recording: true, feed: "https://assets.mixkit.co/videos/preview/mixkit-hallway-in-a-modern-office-building-42940-large.mp4" },
{ id: 9, name: "后院", location: "建筑后方", status: "offline", recording: false, feed: "" },
{ id: 10, name: "接待处", location: "一楼前台", status: "online", recording: true, feed: "https://assets.mixkit.co/videos/preview/mixkit-empty-reception-of-a-modern-hotel-42938-large.mp4" }
];
// 当前选中的摄像头
let selectedCameraId = 1;
// 当前布局模式
let currentLayout = 'nine';
// 视频播放器数组
let videoPlayers = [];
// DOM 加载完成后初始化
document.addEventListener('DOMContentLoaded', function() {
initCameraList();
initVideoGrid();
initLayoutControls();
updateTime();
setInterval(updateTime, 1000);
// 模拟视频播放
simulateVideoPlayback();
});
// 初始化摄像头列表
function initCameraList() {
const cameraList = document.getElementById('camera-list');
cameras.forEach(camera => {
const cameraItem = document.createElement('div');
cameraItem.className = `camera-item ${camera.id === selectedCameraId ? 'active' : ''}`;
cameraItem.dataset.id = camera.id;
// 根据状态设置图标颜色
let iconColor = '#238636'; // 默认绿色
let iconClass = 'fas fa-video';
if (camera.status === 'offline') {
iconColor = '#6e7681';
iconClass = 'fas fa-video-slash';
}
cameraItem.innerHTML = `
<div class="camera-icon" style="background-color: ${iconColor};">
<i class="${iconClass}"></i>
</div>
<div class="camera-info">
<div class="camera-name">${camera.name}</div>
<div class="camera-location">${camera.location}</div>
</div>
<div class="camera-status ${camera.status} ${camera.recording ? 'recording' : ''}"></div>
`;
cameraItem.addEventListener('click', function() {
// 移除所有active类
document.querySelectorAll('.camera-item').forEach(item => {
item.classList.remove('active');
});
// 添加active类到当前项
this.classList.add('active');
// 更新选中的摄像头
selectedCameraId = parseInt(this.dataset.id);
// 在第一个可用的视频播放器中播放选中的摄像头
playCameraInFirstAvailable(selectedCameraId);
});
cameraList.appendChild(cameraItem);
});
// 更新在线/离线计数
updateCameraCount();
}
// 初始化视频网格
function initVideoGrid() {
const videoGrid = document.getElementById('video-grid');
// 创建9个视频播放器
for (let i = 0; i < 9; i++) {
const cameraIndex = i < cameras.length ? i : 0;
const camera = cameras[cameraIndex];
const videoPlayer = document.createElement('div');
videoPlayer.className = 'video-player';
videoPlayer.dataset.index = i;
videoPlayer.innerHTML = `
<div class="video-header">
<div class="video-title">
<i class="fas fa-video"></i>
<span class="title-text">${camera.name}</span>
</div>
<div class="video-controls">
<button class="control-btn" title="全屏">
<i class="fas fa-expand"></i>
</button>
<button class="control-btn" title="声音">
<i class="fas fa-volume-up"></i>
</button>
<button class="control-btn" title="设置">
<i class="fas fa-cog"></i>
</button>
</div>
</div>
<div class="video-container">
${camera.status === 'online' ?
`<video class="video-feed" muted loop>
<source src="${camera.feed}" type="video/mp4">
您的浏览器不支持视频标签。
</video>` :
`<div class="no-video">
<i class="fas fa-wifi-slash"></i>
<p>摄像头离线</p>
</div>`
}
${camera.recording ?
`<div class="playing-indicator">
<i class="fas fa-circle"></i> 录制中
</div>` : ''
}
</div>
`;
videoGrid.appendChild(videoPlayer);
// 保存视频元素引用
if (camera.status === 'online') {
const videoElement = videoPlayer.querySelector('video');
if (videoElement) {
videoPlayers.push(videoElement);
}
}
// 添加点击事件,点击视频播放器时选中对应的摄像头
videoPlayer.addEventListener('click', function(e) {
if (!e.target.closest('.video-controls')) {
const cameraId = cameras[cameraIndex].id;
selectedCameraId = cameraId;
// 更新摄像头列表选中状态
document.querySelectorAll('.camera-item').forEach(item => {
item.classList.remove('active');
if (parseInt(item.dataset.id) === cameraId) {
item.classList.add('active');
}
});
}
});
// 全屏按钮事件
const fullscreenBtn = videoPlayer.querySelector('.control-btn');
fullscreenBtn.addEventListener('click', function() {
const videoContainer = videoPlayer.querySelector('.video-container');
if (videoContainer.requestFullscreen) {
videoContainer.requestFullscreen();
}
});
}
}
// 初始化布局控制
function initLayoutControls() {
const layoutBtns = document.querySelectorAll('.layout-btn');
layoutBtns.forEach(btn => {
btn.addEventListener('click', function() {
// 移除所有active类
layoutBtns.forEach(b => b.classList.remove('active'));
// 添加active类到当前按钮
this.classList.add('active');
// 更新布局
const layout = this.dataset.layout;
changeLayout(layout);
});
});
}
// 更改布局
function changeLayout(layout) {
const videoGrid = document.getElementById('video-grid');
currentLayout = layout;
// 更新网格类
videoGrid.className = 'video-grid ' + layout;
// 根据布局显示/隐藏视频播放器
const videoPlayers = document.querySelectorAll('.video-player');
if (layout === 'single') {
// 单画面:只显示第一个,其他隐藏
videoPlayers.forEach((player, index) => {
player.style.display = index === 0 ? 'flex' : 'none';
});
} else if (layout === 'four') {
// 四画面显示前4个其他隐藏
videoPlayers.forEach((player, index) => {
player.style.display = index < 4 ? 'flex' : 'none';
});
} else {
// 九画面:显示所有
videoPlayers.forEach(player => {
player.style.display = 'flex';
});
}
}
// 在第一个可用的视频播放器中播放选中的摄像头
function playCameraInFirstAvailable(cameraId) {
const camera = cameras.find(c => c.id === cameraId);
if (!camera || camera.status === 'offline') return;
// 根据布局找到第一个可用的视频播放器
let targetIndex = 0;
if (currentLayout === 'single') {
targetIndex = 0;
} else if (currentLayout === 'four') {
// 在四宫格中寻找第一个不是正在录制重要视频的播放器
for (let i = 0; i < 4; i++) {
const currentCamera = cameras[i];
if (!currentCamera.recording) {
targetIndex = i;
break;
}
}
} else {
// 在九宫格中寻找第一个不是正在录制重要视频的播放器
for (let i = 0; i < 9; i++) {
const currentCamera = cameras[i];
if (!currentCamera.recording) {
targetIndex = i;
break;
}
}
}
// 更新目标视频播放器
const videoPlayer = document.querySelectorAll('.video-player')[targetIndex];
if (!videoPlayer) return;
// 更新标题
const titleText = videoPlayer.querySelector('.title-text');
titleText.textContent = camera.name;
// 更新视频源
const videoContainer = videoPlayer.querySelector('.video-container');
videoContainer.innerHTML = `
<video class="video-feed" muted autoplay loop>
<source src="${camera.feed}" type="video/mp4">
您的浏览器不支持视频标签。
</video>
${camera.recording ?
`<div class="playing-indicator">
<i class="fas fa-circle"></i> 录制中
</div>` : ''
}
`;
// 更新录制指示器
const recordingIndicator = videoPlayer.querySelector('.playing-indicator');
if (camera.recording && !recordingIndicator) {
const indicator = document.createElement('div');
indicator.className = 'playing-indicator';
indicator.innerHTML = '<i class="fas fa-circle"></i> 录制中';
videoContainer.appendChild(indicator);
}
// 播放视频
const videoElement = videoContainer.querySelector('video');
if (videoElement) {
videoElement.play().catch(e => console.log("自动播放被阻止:", e));
// 更新视频播放器数组
if (!videoPlayers.includes(videoElement)) {
videoPlayers.push(videoElement);
}
}
}
// 更新在线/离线摄像头计数
function updateCameraCount() {
const onlineCount = cameras.filter(c => c.status === 'online').length;
const offlineCount = cameras.length - onlineCount;
document.getElementById('online-count').textContent = onlineCount;
document.getElementById('offline-count').textContent = offlineCount;
}
// 更新时间显示
function updateTime() {
const now = new Date();
const timeString = now.toLocaleTimeString('zh-CN');
const dateString = now.toLocaleDateString('zh-CN');
document.getElementById('current-time').textContent = timeString;
document.getElementById('last-update').textContent = `${dateString} ${timeString}`;
}
// 模拟视频播放
function simulateVideoPlayback() {
// 播放所有在线的视频
videoPlayers.forEach(video => {
video.play().catch(e => console.log("视频播放失败:", e));
});
// 随机切换一些摄像头的状态(模拟实时变化)
setInterval(() => {
// 随机选择一个摄像头切换在线状态10%概率)
if (Math.random() < 0.1) {
const randomIndex = Math.floor(Math.random() * cameras.length);
const camera = cameras[randomIndex];
// 切换状态
if (camera.status === 'online') {
camera.status = 'offline';
} else {
camera.status = 'online';
}
// 更新UI
updateCameraUI(randomIndex);
updateCameraCount();
}
// 随机开始/停止录制5%概率)
if (Math.random() < 0.05) {
const randomIndex = Math.floor(Math.random() * cameras.length);
const camera = cameras[randomIndex];
if (camera.status === 'online') {
camera.recording = !camera.recording;
updateRecordingUI(randomIndex);
}
}
}, 5000);
}
// 更新摄像头UI状态
function updateCameraUI(index) {
const camera = cameras[index];
const cameraItem = document.querySelector(`.camera-item[data-id="${camera.id}"]`);
if (!cameraItem) return;
// 更新状态指示灯
const statusIndicator = cameraItem.querySelector('.camera-status');
statusIndicator.className = 'camera-status ' + camera.status;
// 更新图标
const cameraIcon = cameraItem.querySelector('.camera-icon i');
if (camera.status === 'offline') {
cameraIcon.className = 'fas fa-video-slash';
cameraItem.querySelector('.camera-icon').style.backgroundColor = '#6e7681';
} else {
cameraIcon.className = 'fas fa-video';
cameraItem.querySelector('.camera-icon').style.backgroundColor = '#238636';
}
// 更新对应的视频播放器
const videoPlayer = document.querySelectorAll('.video-player')[index];
if (videoPlayer) {
const videoContainer = videoPlayer.querySelector('.video-container');
if (camera.status === 'online') {
videoContainer.innerHTML = `
<video class="video-feed" muted autoplay loop>
<source src="${camera.feed}" type="video/mp4">
您的浏览器不支持视频标签。
</video>
${camera.recording ?
`<div class="playing-indicator">
<i class="fas fa-circle"></i> 录制中
</div>` : ''
}
`;
// 播放视频
const videoElement = videoContainer.querySelector('video');
if (videoElement) {
videoElement.play().catch(e => console.log("视频播放失败:", e));
// 添加到视频播放器数组
if (!videoPlayers.includes(videoElement)) {
videoPlayers.push(videoElement);
}
}
} else {
videoContainer.innerHTML = `
<div class="no-video">
<i class="fas fa-wifi-slash"></i>
<p>摄像头离线</p>
</div>
`;
}
}
}
// 更新录制UI状态
function updateRecordingUI(index) {
const camera = cameras[index];
const videoPlayer = document.querySelectorAll('.video-player')[index];
if (!videoPlayer) return;
// 更新录制指示器
const videoContainer = videoPlayer.querySelector('.video-container');
let recordingIndicator = videoContainer.querySelector('.playing-indicator');
if (camera.recording) {
if (!recordingIndicator) {
recordingIndicator = document.createElement('div');
recordingIndicator.className = 'playing-indicator';
recordingIndicator.innerHTML = '<i class="fas fa-circle"></i> 录制中';
videoContainer.appendChild(recordingIndicator);
}
// 更新摄像头列表中的状态指示灯
const cameraItem = document.querySelector(`.camera-item[data-id="${camera.id}"]`);
if (cameraItem) {
const statusIndicator = cameraItem.querySelector('.camera-status');
statusIndicator.classList.add('recording');
}
} else {
if (recordingIndicator) {
recordingIndicator.remove();
}
// 更新摄像头列表中的状态指示灯
const cameraItem = document.querySelector(`.camera-item[data-id="${camera.id}"]`);
if (cameraItem) {
const statusIndicator = cameraItem.querySelector('.camera-status');
statusIndicator.classList.remove('recording');
}
}
}
</script>
</body>
</html>