文件
screen_controller/templates/player.html
T
wangchuanli efc80ca1e0 feat: 初始化屏幕播放控制系统
搭建基于 Flask 的多屏幕定时播放控制后端,提供设备管理控制台、播放端页面、媒体上传与配置文件持久化,并补充项目文档、示例配置及 MIT 许可证。
2026-08-24 10:51:18 +08:00

172 行
6.6 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>定时播放器</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body {
width: 100%; height: 100%;
background: #000; color: #fff;
font-family: "Microsoft YaHei", "PingFang SC", sans-serif;
overflow: hidden;
}
#player-container {
width: 100vw; height: 100vh;
display: flex; flex-direction: column;
align-items: center; justify-content: center;
position: relative;
}
#content {
width: 100%; height: 100%;
display: flex; align-items: center; justify-content: center;
}
#content img, #content video {
max-width: 100%; max-height: 100%;
object-fit: contain;
}
#current-status {
position: absolute; bottom: 16px; left: 50%;
transform: translateX(-50%);
font-size: 14px; color: rgba(255, 255, 255, 0.7);
background: rgba(0, 0, 0, 0.4);
padding: 6px 14px; border-radius: 20px;
white-space: nowrap; pointer-events: none;
}
#device-id {
position: absolute; top: 16px; left: 16px;
font-size: 14px; color: rgba(255, 255, 255, 0.5);
background: rgba(0, 0, 0, 0.4);
padding: 4px 10px; border-radius: 6px;
}
/* 时钟(用于校准本地时间) */
#clock {
position: absolute; top: 16px; right: 16px;
font-size: 14px; color: rgba(255, 255, 255, 0.5);
background: rgba(0, 0, 0, 0.4);
padding: 4px 10px; border-radius: 6px;
}
</style>
</head>
<body>
<div id="device-id"></div>
<div id="clock"></div>
<div id="player-container">
<div id="content"></div>
<p id="current-status">正在初始化...</p>
</div>
<script>
const url = new URL(window.location.href);
const params = new URLSearchParams(url.search);
// device_id 支持 ?device_id=xxx 或 /player/xxx
const deviceId = params.get("device_id") || (url.pathname.split("/").pop() !== "player" ? url.pathname.split("/").pop() : null);
let playlist = [];
let timer = null; // 待播放的 setTimeout 句柄
let pollTimer = null; // 轮询句柄
let currentItem = null;
if (deviceId) {
document.getElementById("device-id").innerText = `设备: ${deviceId}`;
} else {
document.getElementById("device-id").innerText = "设备: 未指定";
document.getElementById("current-status").innerText = "请在 URL 中指定 device_id,例如 /player?device_id=d1";
}
// 实时时钟
function tickClock() {
const now = new Date();
document.getElementById("clock").innerText = now.toLocaleString();
}
setInterval(tickClock, 1000);
tickClock();
// 从服务端获取播放列表
async function fetchPlaylist() {
if (!deviceId) return;
try {
const resp = await fetch(`/get_playlist/${deviceId}`);
if (!resp.ok) {
document.getElementById("current-status").innerText = "获取配置失败:设备不存在";
return;
}
const data = await resp.json();
const newPlaylist = (data.playlist || []).map(item => ({
...item,
start_time: new Date(item.start_time),
}));
// 仅在内容变化时重新调度,避免打断正在播放的视频
if (JSON.stringify(newPlaylist) !== JSON.stringify(playlist)) {
playlist = newPlaylist;
if (!currentItem) scheduleNext();
}
} catch (err) {
console.error("获取播放列表失败:", err);
}
}
// 调度下一条应播放的内容
function scheduleNext() {
const now = new Date();
const upcoming = playlist.find(item => item.start_time > now);
if (upcoming) {
const wait = Math.max(0, upcoming.start_time - now);
document.getElementById("current-status").innerText =
`即将播放: ${upcoming.path} @ ${upcoming.start_time.toLocaleString()}`;
clearTimeout(timer);
timer = setTimeout(() => playContent(upcoming), wait);
} else {
document.getElementById("current-status").innerText = "当前没有待播放内容";
}
}
// 播放指定内容
function playContent(item) {
currentItem = item;
const box = document.getElementById("content");
box.innerHTML = "";
if (item.type === "video") {
const video = document.createElement("video");
video.src = item.path;
video.controls = false; // 展厅全屏场景无需控件
video.autoplay = true;
video.muted = false;
video.playsInline = true;
video.addEventListener("ended", onMediaEnded);
box.appendChild(video);
video.play().catch(e => console.warn("自动播放被阻止:", e));
} else if (item.type === "image") {
const img = document.createElement("img");
img.src = item.path;
img.addEventListener("load", () => {
// 图片默认展示 10 秒后切换(可通过 duration 字段自定义秒数)
const dur = (item.duration || 10) * 1000;
clearTimeout(timer);
timer = setTimeout(onMediaEnded, dur);
});
box.appendChild(img);
} else {
// 未知类型直接结束
clearTimeout(timer);
timer = setTimeout(onMediaEnded, 2000);
}
document.getElementById("current-status").innerText = `正在播放: ${item.path}`;
}
// 单条内容播放结束后,调度下一条
function onMediaEnded() {
currentItem = null;
scheduleNext();
}
// 启动:每 30 秒轮询一次服务端(注释修正)
fetchPlaylist();
pollTimer = setInterval(fetchPlaylist, 30000);
</script>
</body>
</html>