erster Upload
This commit is contained in:
181
templates/display.html
Normal file
181
templates/display.html
Normal file
@@ -0,0 +1,181 @@
|
||||
<!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 (_) {}
|
||||
}
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
poll();
|
||||
setInterval(poll, 1500);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user