Files
signage/templates/player.html
Erik Thiele 0fa3c00319 Version 3.6 mit URL Zoom Funktion
Co-authored-by: Copilot <copilot@github.com>
2026-04-27 13:03:45 +02:00

273 lines
6.8 KiB
HTML
Executable File

<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>CANCOM Simple Signage Player</title>
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
<style>
html, body {
font-family: system-ui, sans-serif;
margin: 0;
padding: 0;
width: 100%;
height: 100%;
background: black;
overflow: hidden;
}
img, video, iframe {
width: 100vw;
height: 100vh;
background: black;
display: none;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 1;
}
img, iframe {
object-fit: contain;
border: 0;
}
video {
object-fit: contain;
}
.newsticker-text {
flex: 1;
overflow: hidden;
position: relative;
height: 100%;
}
.newsticker-track {
display: inline-block;
white-space: nowrap;
position: absolute;
left: 100%;
top: 0;
height: 40px;
line-height: 40px;
animation: ticker-scroll 40s linear infinite;
}
.newsticker-track span {
padding-right: 4rem;
}
@keyframes ticker-scroll {
from { transform: translateX(0); }
to { transform: translateX(-100vw); }
}
</style>
</head>
<body>
<img id="image">
<video id="video" muted autoplay playsinline></video>
<iframe id="iframe"></iframe>
{% if newsticker_enabled %}
<div id="newsticker-bar" style="position:fixed;left:0;right:0;bottom:0;height:40px;z-index:10000;background:#DA002D;color:#fff;display:flex;align-items:center;overflow:hidden;">
<div id="newsticker-text" class="newsticker-text">
<div class="newsticker-track">
<span>{{ newsticker_text }}</span>
</div>
</div>
<div id="newsticker-clock" style="padding:0 18px 0 24px;font-size:1.1em;font-family:monospace;min-width:90px;text-align:right;"></div>
</div>
{% endif %}
<script>
// Newsticker Uhrzeit
function updateClock() {
const el = document.getElementById('newsticker-clock');
if (!el) return;
const now = new Date();
el.textContent = now.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit', second: '2-digit' });
}
setInterval(updateClock, 1000);
updateClock();
/* -----------------------------
Daten vom Server
----------------------------- */
const normalFiles = {{ normal_files | tojson }};
const prioFiles = {{ prio_files | tojson }};
const interval = {{ interval }} * 1000;
const screen = "{{ screen }}";
/* -----------------------------
Player State
----------------------------- */
let normalIndex = 0;
let prioIndex = 0;
let mode = "normal"; // "normal" oder "prio"
const img = document.getElementById("image");
const vid = document.getElementById("video");
const iframe = document.getElementById("iframe");
/* -----------------------------
Hilfsfunktionen
----------------------------- */
function normalizeItem(item) {
if (typeof item === "string") {
const lower = item.toLowerCase();
if (lower.startsWith("http://") || lower.startsWith("https://")) {
return { kind: "url", url: item, zoom: 1.0 };
}
return { kind: "file", name: item };
}
// Item is already an object (from backend) - ensure zoom is set
if (item.kind === "url" && !("zoom" in item)) {
item.zoom = 1.0;
}
return item;
}
function isVideo(item) {
return item.kind === "file" && item.name.toLowerCase().endsWith(".mp4");
}
function isImage(item) {
return item.kind === "file" && /\.(jpg|jpeg|png)$/i.test(item.name);
}
function getNextItem() {
const normalizedNormal = normalFiles.map(normalizeItem);
const normalizedPrio = prioFiles.map(normalizeItem);
// ✅ Sonderfall: nur Priority vorhanden
if (normalizedNormal.length === 0 && normalizedPrio.length > 0) {
return {
item: normalizedPrio[prioIndex++ % normalizedPrio.length],
isPrio: true
};
}
// ✅ Sonderfall: nur normale Playlist vorhanden
if (normalizedPrio.length === 0 && normalizedNormal.length > 0) {
return {
item: normalizedNormal[normalIndex++ % normalizedNormal.length],
isPrio: false
};
}
// ✅ Normal-Phase
if (mode === "normal") {
const item = normalizedNormal[normalIndex++];
if (normalIndex >= normalizedNormal.length) {
normalIndex = 0;
if (normalizedPrio.length > 0) {
mode = "prio"; // ➜ nach kompletter Normal-Playlist wechseln
}
}
return { item, isPrio: false };
}
// ✅ Priority-Phase
if (mode === "prio") {
const item = normalizedPrio[prioIndex++];
if (prioIndex >= normalizedPrio.length) {
prioIndex = 0;
mode = "normal"; // ➜ nach kompletter Priority zurück
}
return { item, isPrio: true };
}
return null;
}
/* -----------------------------
Medien abspielen
----------------------------- */
function playNext() {
const entry = getNextItem();
if (!entry) return;
const item = entry.item;
const basePath = entry.isPrio ? "priority" : screen;
img.style.display = "none";
vid.style.display = "none";
iframe.style.display = "none";
vid.pause();
vid.src = "";
iframe.src = "";
iframe.style.transform = "scale(1)"; // Reset zoom
if (item.kind === "url") {
iframe.style.display = "block";
iframe.src = item.url;
// Apply zoom factor with browser-like behavior
// Zoom < 1.0: mehr Inhalt sichtbar (wie Browser herauszoomen)
// Zoom > 1.0: weniger Inhalt sichtbar (wie Browser reinzoomen)
const zoom = item.zoom || 1.0;
if (zoom !== 1.0) {
// Adjust iframe size and scale to simulate real browser zoom
const scale = 1 / zoom; // Inverted for proper zoom behavior
iframe.style.width = `${100 * scale}vw`;
iframe.style.height = `${100 * scale}vh`;
iframe.style.transform = `translate(-50%, -50%) scale(${zoom})`;
} else {
iframe.style.width = `100vw`;
iframe.style.height = `100vh`;
iframe.style.transform = `translate(-50%, -50%)`;
}
setTimeout(playNext, interval);
return;
}
const src = `/media/${basePath}/${item.name}`;
if (isVideo(item)) {
vid.style.display = "block";
vid.src = src;
vid.onended = playNext;
vid.play();
} else {
img.style.display = "block";
img.src = src;
setTimeout(playNext, interval);
}
}
/* -----------------------------
Auto-Reload bei Änderungen
----------------------------- */
let lastHash = null;
async function checkForUpdates() {
try {
const res = await fetch(`/playlist/${screen}/hash`, { cache: "no-store" });
const hash = await res.text();
if (lastHash && lastHash !== hash) {
location.reload();
}
lastHash = hash;
} catch (e) {
console.warn("Playlist-Check fehlgeschlagen", e);
}
}
/* -----------------------------
Start
----------------------------- */
playNext();
setInterval(checkForUpdates, 5000);
</script>
</body>
</html>