first commit

This commit is contained in:
duffyduck
2026-08-22 20:34:42 +02:00
commit 9546b9a4c6
1586 changed files with 172520 additions and 0 deletions
@@ -0,0 +1,207 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<title>Screen Cast</title>
<style>
html, body {
margin: 0; padding: 0; height: 100%; width: 100%;
background: #000; overflow: hidden; cursor: none;
}
#screen {
position: fixed; inset: 0;
width: 100%; height: 100%;
object-fit: contain; /* ganzer Bildschirm sichtbar */
background: #000;
}
#screen.fill { object-fit: cover; } /* randlos, schneidet Ränder ab */
#overlay {
position: fixed; inset: 0;
display: flex; flex-direction: column;
align-items: center; justify-content: center;
background: rgba(0,0,0,.86);
color: #fff; font-family: system-ui, "Segoe UI", Roboto, sans-serif;
text-align: center; transition: opacity .4s; z-index: 10;
}
#overlay.hidden { opacity: 0; pointer-events: none; }
#title { font-size: 5vh; font-weight: 700; letter-spacing: .5px; }
#status { margin-top: 2vh; font-size: 2.6vh; opacity: .8; }
#keys { margin-top: 5vh; font-size: 2vh; opacity: .5; line-height: 1.9; }
.dot {
display: inline-block; width: 1.4vh; height: 1.4vh; border-radius: 50%;
background: #ff5252; margin-right: 1vh; animation: pulse 1.2s infinite;
}
@keyframes pulse { 50% { opacity: .25; } }
</style>
</head>
<body>
<video id="screen" autoplay muted playsinline></video>
<div id="overlay">
<div id="title">Screen Cast</div>
<div id="status"><span class="dot"></span>Verbinde …</div>
<div id="keys">OK / Klick = Vollbild &nbsp;·&nbsp; F = Bild füllen oder einpassen</div>
</div>
<script>
(function () {
var video = document.getElementById('screen');
var overlay = document.getElementById('overlay');
var status = document.getElementById('status');
var ws = null, mediaSource = null, sourceBuffer = null;
var queue = [], mimeType = '', gotFirstFrame = false;
var hideTimer = null, retryDelay = 700;
function say(text, sticky) {
status.innerHTML = '<span class="dot"></span>' + text;
overlay.classList.remove('hidden');
clearTimeout(hideTimer);
if (!sticky) hideTimer = setTimeout(function () {
overlay.classList.add('hidden');
}, 1800);
}
// ---------------------------------------------------------------- MSE
function resetPlayer(mime) {
mimeType = mime || mimeType;
queue = [];
gotFirstFrame = false;
if (sourceBuffer) { try { mediaSource.removeSourceBuffer(sourceBuffer); } catch (e) {} }
sourceBuffer = null;
mediaSource = new MediaSource();
video.src = URL.createObjectURL(mediaSource);
mediaSource.addEventListener('sourceopen', function () {
try {
sourceBuffer = mediaSource.addSourceBuffer(mimeType);
sourceBuffer.mode = 'segments';
sourceBuffer.addEventListener('updateend', pump);
pump();
} catch (e) {
say('Dieser Fernseher kann das Videoformat nicht abspielen (' + mimeType + ')', true);
}
});
video.play().catch(function () {});
}
function pump() {
if (!sourceBuffer || sourceBuffer.updating || queue.length === 0) return;
// Bei Rückstand die ältesten Fragmente überspringen: lieber ein Sprung
// als eine wachsende Verzögerung.
if (queue.length > 60) queue.splice(0, queue.length - 20);
var chunk = queue.shift();
try {
sourceBuffer.appendBuffer(chunk);
} catch (e) {
if (e.name === 'QuotaExceededError') {
trimBuffer(true);
queue.unshift(chunk);
}
}
}
/** Alte Daten wegwerfen, sonst läuft der Speicher des Fernsehers voll. */
function trimBuffer(aggressive) {
if (!sourceBuffer || sourceBuffer.updating) return;
try {
var buffered = sourceBuffer.buffered;
if (!buffered.length) return;
var keep = aggressive ? 2 : 12;
var cutoff = video.currentTime - keep;
if (cutoff > buffered.start(0) + 0.5) sourceBuffer.remove(buffered.start(0), cutoff);
} catch (e) {}
}
/** Hält die Verzögerung klein: notfalls ans Live-Ende springen. */
function keepLive() {
if (!sourceBuffer || !video.buffered.length) return;
var end = video.buffered.end(video.buffered.length - 1);
var behind = end - video.currentTime;
if (behind > 2.5 || video.currentTime === 0) {
video.currentTime = Math.max(0, end - 0.2);
video.playbackRate = 1.0;
} else if (behind > 0.7) {
video.playbackRate = 1.08; // sanft aufholen statt springen
} else {
video.playbackRate = 1.0;
}
if (video.paused) video.play().catch(function () {});
}
setInterval(function () { trimBuffer(false); keepLive(); }, 1000);
// --------------------------------------------------------- Verbindung
function connect() {
var proto = location.protocol === 'https:' ? 'wss://' : 'ws://';
ws = new WebSocket(proto + location.host + '/ws');
ws.binaryType = 'arraybuffer';
ws.onopen = function () {
retryDelay = 700;
say('Verbunden warte auf Bild …', true);
};
ws.onmessage = function (event) {
if (typeof event.data === 'string') {
var msg = JSON.parse(event.data);
if (msg.type === 'init') {
var mime = 'video/mp4; codecs="' + msg.codec + '"';
if (window.MediaSource && !MediaSource.isTypeSupported(mime)) {
mime = 'video/mp4; codecs="avc1.42E01E"'; // Notfall-Fallback
}
resetPlayer(mime);
} else if (msg.type === 'reset') {
resetPlayer(mimeType);
}
return;
}
queue.push(new Uint8Array(event.data));
if (!gotFirstFrame) {
gotFirstFrame = true;
say('Los gehts', false);
}
pump();
};
ws.onclose = function () {
say('Verbindung getrennt neuer Versuch …', true);
setTimeout(connect, retryDelay);
retryDelay = Math.min(retryDelay * 1.6, 5000);
};
ws.onerror = function () { try { ws.close(); } catch (e) {} };
}
if (!window.MediaSource) {
say('Dieser Browser unterstützt kein Live-Video. Versuche stattdessen ' +
location.origin + '/stream.ts in VLC.', true);
} else {
connect();
}
// --------------------------------------------------------- Bedienung
function goFullscreen() {
var el = document.documentElement;
var request = el.requestFullscreen || el.webkitRequestFullscreen || el.msRequestFullscreen;
if (request) request.call(el).catch(function () {});
}
function toggleFill() {
video.classList.toggle('fill');
say(video.classList.contains('fill') ? 'Bild füllt den Schirm' : 'Ganzes Bild sichtbar', false);
}
document.addEventListener('click', function () { goFullscreen(); video.play().catch(function(){}); });
document.addEventListener('keydown', function (e) {
if (e.key === 'f' || e.key === 'F') { toggleFill(); return; }
if (e.key === 'Enter' || e.keyCode === 13) { goFullscreen(); }
video.play().catch(function () {});
});
})();
</script>
</body>
</html>