Files
miracast-client/android/app/build/intermediates/assets/release/mergeReleaseAssets/player.html
T
2026-08-22 20:34:42 +02:00

208 lines
6.9 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, 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>