Files
signage/templates/player.html
2026-06-07 12:35:10 +02:00

322 lines
10 KiB
HTML
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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>
/* ─── Player-Grundlayout ───
Alle Medien (img/video/iframe) werden zentriert auf Vollbild angezeigt.
Standardmäßig ausgeblendet nur das aktive Element ist sichtbar. */
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 (Laufband) ─── */
.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>
<!-- ═══════════════════════════════════════════════════
OVERLAY / Custom-URL (iframe-Modus)
Wenn der Aktions-Button angeklickt wird, öffnet
sich dieser Overlay-Bereich mit Zurück-Button.
═══════════════════════════════════════════════════ -->
<div id="overlay" style="display:none;position:fixed;inset:0;z-index:99999;background:#fff;">
<button onclick="closeOverlay()"
style="position:absolute;top:16px;left:16px;z-index:10;
padding:8px 16px;background:#DA002D;color:#fff;
border:none;border-radius:6px;font-size:1rem;cursor:pointer;">
← Zurück
</button>
<iframe id="overlay-iframe" style="width:100%;height:100%;border:none;display:block;"></iframe>
</div>
<!-- ─── Custom-URL-Button (oben links) ─── -->
{% if custom_url and custom_url_enabled %}
{% if custom_url_target == "redirect" %}
<!-- Direkt-Weiterleitung (verlässt den Player) -->
<a href="{{ custom_url }}"
style="position:fixed;top:16px;left:16px;z-index:9999;
padding:10px 20px;background:#DA002D;color:#fff;
border:none;border-radius:8px;font-size:1rem;cursor:pointer;
text-decoration:none;box-shadow:0 2px 8px rgba(0,0,0,0.3);">
{{ custom_url_label or "Info" }}
</a>
{% else %}
<!-- iframe-Overlay (bleibt im Player, Zurück-Button) -->
<button onclick='openOverlay({{ custom_url | tojson }})'
style="position:fixed;top:16px;left:16px;z-index:9999;
padding:10px 20px;background:#DA002D;color:#fff;
border:none;border-radius:8px;font-size:1rem;cursor:pointer;
box-shadow:0 2px 8px rgba(0,0,0,0.3);">
{{ custom_url_label or "Info" }}
</button>
{% endif %}
{% endif %}
<!-- ─── Medien-Elemente (eines ist jeweils sichtbar) ─── -->
<img id="image">
<video id="video" muted autoplay playsinline></video>
<iframe id="iframe"></iframe>
<!-- ─── Newsticker (optional, Laufband unten) ─── -->
{% 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>
// ═════════════════════════════════════════════════════
// Player-JavaScript
// ═════════════════════════════════════════════════════
let playerTimer = null;
// ─── Custom-URL-Overlay ───
function openOverlay(url) {
if (playerTimer) clearTimeout(playerTimer);
document.getElementById("overlay").style.display = "block";
document.getElementById("overlay-iframe").src = url;
}
function closeOverlay() {
document.getElementById("overlay").style.display = "none";
document.getElementById("overlay-iframe").src = "";
playNext();
}
// ─── Newsticker-Uhr (aktuelle 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();
// ─── Vom Server übergebene Daten ───
const normalFiles = {{ normal_files | tojson }};
const prioFiles = {{ prio_files | tojson }};
const interval = {{ interval }} * 1000;
const screen = "{{ screen }}";
const site = "{{ site }}";
// ─── Player-Status ───
let normalIndex = 0;
let prioIndex = 0;
let mode = "normal"; // "normal" | "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 };
}
if (item.kind === "url" && !("zoom" in item)) {
item.zoom = 1.0;
}
if (!("enabled" in item)) {
item.enabled = true;
}
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 isHtml(item) {
return item.kind === "file" && /\.(html?|htm)$/i.test(item.name);
}
// ─── Nächstes Element aus der Playlist holen ───
// Ablauf: Normal-Playlist → Priority → Normal → …
function getNextItem() {
const normalizedNormal = normalFiles.map(normalizeItem);
const normalizedPrio = prioFiles.map(normalizeItem);
const activeNormal = normalizedNormal.filter(item => item.enabled !== false);
const activePrio = normalizedPrio.filter(item => item.enabled !== false);
// Nur Priority vorhanden
if (activeNormal.length === 0 && activePrio.length > 0) {
return { item: activePrio[prioIndex++ % activePrio.length], isPrio: true };
}
// Nur normale Playlist vorhanden
if (activePrio.length === 0 && activeNormal.length > 0) {
return { item: activeNormal[normalIndex++ % activeNormal.length], isPrio: false };
}
// Normal-Phase
if (mode === "normal") {
const item = activeNormal[normalIndex++];
if (normalIndex >= activeNormal.length) {
normalIndex = 0;
if (activePrio.length > 0) mode = "prio";
}
return { item, isPrio: false };
}
// Priority-Phase
if (mode === "prio") {
const item = activePrio[prioIndex++];
if (prioIndex >= activePrio.length) {
prioIndex = 0;
mode = "normal";
}
return { item, isPrio: true };
}
return null;
}
// ─── Aktuelles Medium abspielen ───
function playNext() {
const entry = getNextItem();
if (!entry) return;
const item = entry.item;
const basePath = entry.isPrio ? "priority" : screen;
// Alle Elemente ausblenden
img.style.display = "none";
vid.style.display = "none";
iframe.style.display = "none";
vid.pause();
vid.src = "";
iframe.src = "";
iframe.style.transform = "scale(1)";
// URL im iframe anzeigen (mit Zoom-Unterstützung)
if (item.kind === "url") {
iframe.style.display = "block";
iframe.src = item.url;
const zoom = item.zoom || 1.0;
if (zoom !== 1.0) {
const scale = 1 / zoom;
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%)`;
}
playerTimer = setTimeout(playNext, interval);
return;
}
// Datei aus dem Medienverzeichnis
const src = entry.isPrio ? `/media/priority/${item.name}` : `/media/${site}/${basePath}/${item.name}`;
if (isVideo(item)) {
vid.style.display = "block";
vid.src = src;
vid.onended = playNext;
vid.play();
} else if (isHtml(item)) {
iframe.style.display = "block";
iframe.src = src;
iframe.style.width = `100vw`;
iframe.style.height = `100vh`;
iframe.style.transform = `translate(-50%, -50%)`;
playerTimer = setTimeout(playNext, interval);
} else {
// Bild (jpg/png)
img.style.display = "block";
img.src = src;
playerTimer = setTimeout(playNext, interval);
}
}
// ─── Auto-Reload bei Playlist-Änderungen ───
let lastHash = null;
async function checkForUpdates() {
try {
const res = await fetch(`/playlist/${site}/${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>