Files
videoplayer/templates/display.html
2026-05-27 23:08:12 +02:00

188 lines
5.6 KiB
HTML
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">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
<title>Videoplayer Display</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body {
width: 100%; height: 100%;
background: #000;
overflow: hidden;
font-family: system-ui, sans-serif;
}
#stage {
width: 100%; height: 100%;
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
#stage video,
#stage img {
width: 100%; height: 100%;
object-fit: contain;
display: none;
}
#stage audio {
display: none;
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
width: 80%;
max-width: 400px;
}
#empty {
color: #444;
font-size: 1.1rem;
}
</style>
</head>
<body>
<div id="stage">
<video id="video" muted playsinline></video>
<audio id="audio" muted controls></audio>
<img id="image" alt="">
<div id="empty">Warte auf Steuerung…</div>
</div>
<script>
const video = document.getElementById('video');
const audio = document.getElementById('audio');
const image = document.getElementById('image');
const empty = document.getElementById('empty');
let lastVersion = -1;
let lastCurrentName = null;
let audioUnlocked = false;
// AudioContext-Trick: entsperrt Audio in Chromium-basierten Browsern
(function unlockAudio() {
try {
const ctx = new (window.AudioContext || window.webkitAudioContext)();
ctx.resume();
const buf = ctx.createBuffer(1, 1, 22050);
const src = ctx.createBufferSource();
src.buffer = buf;
src.connect(ctx.destination);
src.start();
// Sobald der Buffer abgespielt wurde, gilt Audio als entsperrt
src.onended = () => { audioUnlocked = true; };
audioUnlocked = true;
} catch (_) {}
})();
// Klick auf die Seite entsperrt Audio endgültig
document.addEventListener('click', () => {
audioUnlocked = true;
video.muted = false;
audio.muted = false;
if (video.src && video.paused) video.play().catch(() => {});
if (audio.src && audio.paused) audio.play().catch(() => {});
}, { once: true });
function showOnly(el) {
[video, audio, image, empty].forEach(e => { e.style.display = 'none'; });
if (el) el.style.display = el === empty ? 'flex' : 'block';
}
async function tryPlay(player) {
player.muted = false;
try {
await player.play();
return true;
} catch (_) {
player.muted = true;
try {
await player.play();
return false;
} catch (_) {
return false;
}
}
}
async function poll() {
try {
const res = await fetch('/api/state');
const state = await res.json();
const versionChanged = state.version !== lastVersion;
if (versionChanged) {
const prevVersion = lastVersion;
lastVersion = state.version;
const currentName = state.current ? state.current.name : null;
const currentChanged = currentName !== lastCurrentName;
lastCurrentName = currentName;
// Seek
if (state.seek && prevVersion >= 0 && state.current &&
(state.current.kind === 'video' || state.current.kind === 'audio')) {
const player = state.current.kind === 'video' ? video : audio;
if (player.duration && isFinite(player.duration)) {
player.currentTime = Math.max(0, Math.min(player.duration, player.currentTime + state.seek));
}
fetch('/api/seek-ack', { method: 'POST' }).catch(() => {});
}
// Neues Medium laden
if (currentChanged) {
if (state.current) {
const src = `/media/${encodeURIComponent(state.current.name)}`;
const kind = state.current.kind;
if (kind === 'video') {
video.src = src;
video.oncanplay = () => {
if (lastCurrentName === state.current?.name && state.playing) {
tryPlay(video);
}
};
showOnly(video);
} else if (kind === 'audio') {
audio.src = src;
audio.oncanplay = () => {
if (lastCurrentName === state.current?.name && state.playing) {
tryPlay(audio);
}
};
showOnly(audio);
} else {
image.src = src;
showOnly(image);
}
} else {
showOnly(empty);
}
}
}
// Play/pause: immer ausführen
if (state.current && (state.current.kind === 'video' || state.current.kind === 'audio')) {
const player = state.current.kind === 'video' ? video : audio;
player.volume = state.volume ?? 1.0;
if (state.playing && player.src) {
tryPlay(player);
} else if (!state.playing) {
player.pause();
}
}
} catch (_) {}
}
function heartbeat() {
fetch('/api/display/ping', { method: 'POST' }).catch(() => {});
}
window.addEventListener('load', () => {
poll();
heartbeat();
setInterval(poll, 1500);
setInterval(heartbeat, 5000);
});
</script>
</body>
</html>