该文本描述了一个智能视频墙的HTML和JavaScript实现。用户可以创建视频容器,支持拖拽和调整大小,并且能够保存布局到本地存储。使用了EasyPlayerPro播放器来播放视频流,提供了多种调整手柄以便用户自定义视频容器的大小和位置。代码中还处理了窗口大小变化的情况,确保视频容器不会超出视口。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>智能视频墙</title>
<script src="/js/EasyPlayer-pro.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #000;
overflow: hidden;
height: 100vh;
}
.video-container {
position: absolute;
cursor: move;
overflow: hidden;
border: 2px solid transparent;
transition: border-color 0.2s;
}
.video-container.selected {
border-color: #2196F3;
}
.resize-handle {
position: absolute;
width: 12px;
height: 12px;
background: #2196F3;
z-index: 2;
opacity: 0;
transition: opacity 0.2s;
}
.selected .resize-handle {
opacity: 1;
}
.resize-nw { left: -6px; top: -6px; cursor: nw-resize; }
.resize-ne { right: -6px; top: -6px; cursor: ne-resize; }
.resize-sw { left: -6px; bottom: -6px; cursor: sw-resize; }
.resize-se { right: -6px; bottom: -6px; cursor: se-resize; }
.resize-n { left: 50%; top: -6px; cursor: n-resize; }
.resize-s { left: 50%; bottom: -6px; cursor: s-resize; }
.resize-w { left: -6px; top: 50%; cursor: w-resize; }
.resize-e { right: -6px; top: 50%; cursor: e-resize; }
.drag-ghost {
opacity: 0.8;
transform: scale(1.02);
}
</style>
</head>
<body>
<script>
const videoSources = [
"ws://183.223.76.216:28080/live/PerwsB0LcQY_01.flv?expired=20250124101429",
"ws://183.223.76.216:28080/live/P7ULxnT47x_01.flv?expired=20250124101527",
"ws://183.223.76.216:28080/live/PhfaWb6Ux0c_01.flv?expired=20250124101020",
"ws://183.223.76.216:28080/live/PubmOrMJoHg_01.flv?expired=20250124101154",
"ws://183.223.76.216:28080/live/PmKfOwWkdZD_01.flv?expired=20250124101609"
];
const STORAGE_KEY = 'videoWallLayout';
let selectedContainer = null;
let isResizing = false;
let isDragging = false;
let dragStartX, dragStartY, initialLeft, initialTop;
function createVideoContainer(url, index) {
const container = document.createElement('div');
container.className = 'video-container';
// 添加调整手柄
const directions = ['nw', 'ne', 'sw', 'se', 'n', 's', 'w', 'e'];
directions.forEach(dir => {
const handle = document.createElement('div');
handle.className = `resize-handle resize-${dir}`;
handle.dataset.direction = dir;
container.appendChild(handle);
});
// 初始化播放器
new EasyPlayerPro({
isLive: true,
isMute: true,
container: container,
videoBuffer: 0.2,
useMSE: true,
showBandwidth: false,
operateBtns: {
fullscreen: false,
screenshot: false,
play: false,
audio: false,
quality: false
}
}).play(url);
// 加载布局
const savedLayout = loadLayout();
if(savedLayout && savedLayout[index]) {
Object.assign(container.style, {
width: savedLayout[index].width,
height: savedLayout[index].height,
left: savedLayout[index].left,
top: savedLayout[index].top
});
} else {
container.style.width = '400px';
container.style.height = '225px';
container.style.left = `${Math.random() * (window.innerWidth - 400)}px`;
container.style.top = `${Math.random() * (window.innerHeight - 225)}px`;
}
// 事件监听
container.addEventListener('mousedown', handleMouseDown);
container.addEventListener('mouseup', handleMouseUp);
document.body.appendChild(container);
return container;
}
function handleMouseDown(e) {
if (e.target.classList.contains('resize-handle')) {
startResize(e);
return;
}
const container = e.currentTarget;
selectContainer(container);
// 立即开始拖拽
startDrag(e);
}
function handleMouseUp() {
if (isDragging) endDrag();
}
function selectContainer(container) {
if (selectedContainer) selectedContainer.classList.remove('selected');
selectedContainer = container;
container.classList.add('selected');
}
function startResize(e) {
isResizing = true;
const container = e.target.parentElement;
const direction = e.target.dataset.direction;
const rect = container.getBoundingClientRect();
const startX = e.clientX;
const startY = e.clientY;
const startWidth = rect.width;
const startHeight = rect.height;
const startLeft = rect.left;
const startTop = rect.top;
function moveHandler(e) {
if (!isResizing) return;
const deltaX = e.clientX - startX;
const deltaY = e.clientY - startY;
switch(direction) {
case 'e':
container.style.width = startWidth + deltaX + 'px';
break;
case 's':
container.style.height = startHeight + deltaY + 'px';
break;
case 'se':
container.style.width = startWidth + deltaX + 'px';
container.style.height = startHeight + deltaY + 'px';
break;
case 'n':
container.style.height = startHeight - deltaY + 'px';
container.style.top = startTop + deltaY + 'px';
break;
case 'w':
container.style.width = startWidth - deltaX + 'px';
container.style.left = startLeft + deltaX + 'px';
break;
case 'ne':
container.style.width = startWidth + deltaX + 'px';
container.style.height = startHeight - deltaY + 'px';
container.style.top = startTop + deltaY + 'px';
break;
case 'nw':
container.style.width = startWidth - deltaX + 'px';
container.style.height = startHeight - deltaY + 'px';
container.style.left = startLeft + deltaX + 'px';
container.style.top = startTop + deltaY + 'px';
break;
case 'sw':
container.style.width = startWidth - deltaX + 'px';
container.style.height = startHeight + deltaY + 'px';
container.style.left = startLeft + deltaX + 'px';
break;
}
}
function upHandler() {
isResizing = false;
saveLayout();
document.removeEventListener('mousemove', moveHandler);
document.removeEventListener('mouseup', upHandler);
}
document.addEventListener('mousemove', moveHandler);
document.addEventListener('mouseup', upHandler);
}
function startDrag(e) {
isDragging = true;
const container = e.currentTarget;
container.classList.add('drag-ghost');
dragStartX = e.clientX;
dragStartY = e.clientY;
initialLeft = parseFloat(container.style.left);
initialTop = parseFloat(container.style.top);
function moveHandler(e) {
if (!isDragging) return;
const deltaX = e.clientX - dragStartX;
const deltaY = e.clientY - dragStartY;
container.style.left = Math.max(0, Math.min(
initialLeft + deltaX,
window.innerWidth - container.offsetWidth
)) + 'px';
container.style.top = Math.max(0, Math.min(
initialTop + deltaY,
window.innerHeight - container.offsetHeight
)) + 'px';
}
function upHandler() {
isDragging = false;
container.classList.remove('drag-ghost');
saveLayout();
document.removeEventListener('mousemove', moveHandler);
document.removeEventListener('mouseup', upHandler);
}
document.addEventListener('mousemove', moveHandler);
document.addEventListener('mouseup', upHandler);
}
function saveLayout() {
const layout = Array.from(document.querySelectorAll('.video-container')).map(container => ({
width: container.style.width,
height: container.style.height,
left: container.style.left,
top: container.style.top
}));
localStorage.setItem(STORAGE_KEY, JSON.stringify(layout));
}
function loadLayout() {
try {
return JSON.parse(localStorage.getItem(STORAGE_KEY));
} catch {
return null;
}
}
window.addEventListener('load', () => {
videoSources.forEach((url, index) => createVideoContainer(url, index));
// 点击空白取消选择
document.addEventListener('click', (e) => {
if (!e.target.closest('.video-container')) {
if (selectedContainer) {
selectedContainer.classList.remove('selected');
selectedContainer = null;
}
}
});
// 窗口大小变化处理
window.addEventListener('resize', () => {
document.querySelectorAll('.video-container').forEach(container => {
container.style.left = Math.min(
parseFloat(container.style.left),
window.innerWidth - container.offsetWidth
) + 'px';
container.style.top = Math.min(
parseFloat(container.style.top),
window.innerHeight - container.offsetHeight
) + 'px';
});
saveLayout();
});
});
</script>
</body>
</html>