Hilfetexte ergänzt

This commit is contained in:
Erik Thiele
2026-06-07 12:35:10 +02:00
parent 0efc5bb346
commit 0e192a615d
10 changed files with 339 additions and 170 deletions

View File

@@ -6,6 +6,9 @@
<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;
@@ -37,6 +40,7 @@
object-fit: contain;
}
/* ─── Newsticker (Laufband) ─── */
.newsticker-text {
flex: 1;
overflow: hidden;
@@ -47,11 +51,9 @@
.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;
@@ -65,12 +67,16 @@
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;
@@ -81,31 +87,35 @@
<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" %}
<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 %}
<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 %}
{% 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">
@@ -117,9 +127,15 @@
</div>
{% endif %}
<script>
// ═════════════════════════════════════════════════════
// Player-JavaScript
// ═════════════════════════════════════════════════════
let playerTimer = null;
// ─── Custom-URL-Overlay ───
function openOverlay(url) {
if (playerTimer) clearTimeout(playerTimer);
document.getElementById("overlay").style.display = "block";
@@ -132,7 +148,7 @@ function closeOverlay() {
playNext();
}
// Newsticker Uhrzeit
// ─── Newsticker-Uhr (aktuelle Uhrzeit) ───
function updateClock() {
const el = document.getElementById('newsticker-clock');
if (!el) return;
@@ -141,29 +157,23 @@ function updateClock() {
}
setInterval(updateClock, 1000);
updateClock();
/* -----------------------------
Daten vom Server
----------------------------- */
// ─── 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 State
----------------------------- */
// ─── Player-Status ───
let normalIndex = 0;
let prioIndex = 0;
let mode = "normal"; // "normal" oder "prio"
let mode = "normal"; // "normal" | "prio"
const img = document.getElementById("image");
const vid = document.getElementById("video");
const iframe = document.getElementById("iframe");
/* -----------------------------
Hilfsfunktionen
----------------------------- */
// ─── Hilfsfunktionen ───
function normalizeItem(item) {
if (typeof item === "string") {
const lower = item.toLowerCase();
@@ -172,7 +182,6 @@ function normalizeItem(item) {
}
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;
}
@@ -185,65 +194,51 @@ function normalizeItem(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);
// ✅ Sonderfall: nur Priority vorhanden
// Nur Priority vorhanden
if (activeNormal.length === 0 && activePrio.length > 0) {
return {
item: activePrio[prioIndex++ % activePrio.length],
isPrio: true
};
return { item: activePrio[prioIndex++ % activePrio.length], isPrio: true };
}
// ✅ Sonderfall: nur normale Playlist vorhanden
// Nur normale Playlist vorhanden
if (activePrio.length === 0 && activeNormal.length > 0) {
return {
item: activeNormal[normalIndex++ % activeNormal.length],
isPrio: false
};
return { item: activeNormal[normalIndex++ % activeNormal.length], isPrio: false };
}
// ✅ Normal-Phase
// Normal-Phase
if (mode === "normal") {
const item = activeNormal[normalIndex++];
if (normalIndex >= activeNormal.length) {
normalIndex = 0;
if (activePrio.length > 0) {
mode = "prio"; // ➜ nach kompletter Normal-Playlist wechseln
}
if (activePrio.length > 0) mode = "prio";
}
return { item, isPrio: false };
}
// ✅ Priority-Phase
// Priority-Phase
if (mode === "prio") {
const item = activePrio[prioIndex++];
if (prioIndex >= activePrio.length) {
prioIndex = 0;
mode = "normal"; // ➜ nach kompletter Priority zurück
mode = "normal";
}
return { item, isPrio: true };
}
return null;
}
/* -----------------------------
Medien abspielen
----------------------------- */
// ─── Aktuelles Medium abspielen ───
function playNext() {
const entry = getNextItem();
if (!entry) return;
@@ -251,25 +246,22 @@ function playNext() {
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)"; // Reset zoom
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;
// 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
const scale = 1 / zoom;
iframe.style.width = `${100 * scale}vw`;
iframe.style.height = `${100 * scale}vh`;
iframe.style.transform = `translate(-50%, -50%) scale(${zoom})`;
@@ -282,6 +274,7 @@ function playNext() {
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";
@@ -296,22 +289,20 @@ function playNext() {
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 Änderungen
----------------------------- */
// ─── 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();
}
@@ -321,9 +312,7 @@ async function checkForUpdates() {
}
}
/* -----------------------------
Start
----------------------------- */
// ─── Start ───
playNext();
setInterval(checkForUpdates, 5000);
</script>