QR-Scan-Fix: höhere Auflösung, Throttle, dontInvert; QR dynamische Größe; Antwort-Rohdaten; Kopierfunktion; CDN → vendor

This commit is contained in:
Ole 2026-06-14 16:15:15 +02:00
parent 16e9359d1c
commit c322e9f31b
2 changed files with 49 additions and 10 deletions

49
app.js
View file

@ -12,6 +12,27 @@
document.body.classList.add('debug-mode'); document.body.classList.add('debug-mode');
} }
document.querySelectorAll('.raw-data').forEach(el => {
el.style.cursor = 'pointer';
el.title = 'Klicken zum Kopieren';
el.addEventListener('click', async () => {
try {
await navigator.clipboard.writeText(el.textContent);
const orig = el.textContent;
el.textContent = 'Kopiert!';
setTimeout(() => { el.textContent = orig; }, 1200);
} catch (e) {
const sel = window.getSelection();
const range = document.createRange();
range.selectNodeContents(el);
sel.removeAllRanges();
sel.addRange(range);
document.execCommand('copy');
sel.removeAllRanges();
}
});
});
/* ─── Screen routing ─── */ /* ─── Screen routing ─── */
function show(name) { function show(name) {
['start','host','join','game','result'].forEach(s => ['start','host','join','game','result'].forEach(s =>
@ -43,7 +64,8 @@
const c = $(containerId); const c = $(containerId);
if (!c) return; if (!c) return;
c.innerHTML = ''; c.innerHTML = '';
qrHost = new QRCode(c, { text: data, width: 200, height: 200, correctLevel: QRCode.CorrectLevel.L }); const size = Math.min(window.innerWidth - 64, 360);
qrHost = new QRCode(c, { text: data, width: size, height: size, correctLevel: QRCode.CorrectLevel.L });
} }
/* ─── Camera ─── */ /* ─── Camera ─── */
@ -52,14 +74,22 @@
async function camStart(videoId) { async function camStart(videoId) {
await camStop(); await camStop();
camStream = await navigator.mediaDevices.getUserMedia({ try {
video: { facingMode: 'environment', width: { ideal: 320 }, height: { ideal: 240 } } camStream = await navigator.mediaDevices.getUserMedia({
}); video: { facingMode: { ideal: 'environment' }, width: { ideal: 1920 }, height: { ideal: 1080 } }
});
} catch (_) {
camStream = await navigator.mediaDevices.getUserMedia({ video: true });
}
const v = $(videoId); const v = $(videoId);
v.srcObject = camStream; v.srcObject = camStream;
v.setAttribute('playsinline', ''); v.setAttribute('playsinline', '');
v.classList.add('active'); v.classList.add('active');
await v.play(); await v.play();
try {
const track = camStream.getVideoTracks()[0];
if (track) track.applyConstraints({ advanced: [{ focusMode: 'continuous' }] });
} catch (_) {}
return v; return v;
} }
@ -71,13 +101,16 @@
function camScan(videoId, canvasId, callback) { function camScan(videoId, canvasId, callback) {
const v = $(videoId); const v = $(videoId);
const c = $(canvasId); const c = $(canvasId);
const ctx = c.getContext('2d'); const ctx = c.getContext('2d', { willReadFrequently: true });
camRaf = requestAnimationFrame(function tick() { let lastScan = 0;
camRaf = requestAnimationFrame(function tick(now) {
if (!v.videoWidth) { camRaf = requestAnimationFrame(tick); return; } if (!v.videoWidth) { camRaf = requestAnimationFrame(tick); return; }
if (now - lastScan < 100) { camRaf = requestAnimationFrame(tick); return; }
lastScan = now;
c.width = v.videoWidth; c.height = v.videoHeight; c.width = v.videoWidth; c.height = v.videoHeight;
ctx.drawImage(v, 0, 0); ctx.drawImage(v, 0, 0);
const img = ctx.getImageData(0, 0, c.width, c.height); const img = ctx.getImageData(0, 0, c.width, c.height);
const code = jsQR(img.data, img.width, img.height); const code = jsQR(img.data, img.width, img.height, { inversionAttempts: 'dontInvert' });
if (code && code.data) { if (code && code.data) {
try { try {
const p = JSON.parse(code.data); const p = JSON.parse(code.data);
@ -244,6 +277,8 @@
mesh.onanswerready = answerData => { mesh.onanswerready = answerData => {
$('join-answer-section').hidden = false; $('join-answer-section').hidden = false;
showQR('qr-answer', answerData); showQR('qr-answer', answerData);
const raw = $('answer-raw');
if (raw) raw.textContent = answerData;
$('join-scan-status').textContent = 'Antwort-QR dem Host zeigen'; $('join-scan-status').textContent = 'Antwort-QR dem Host zeigen';
camStop(); camStop();
}; };

View file

@ -69,6 +69,10 @@
<p>Antwort-QR für den Host:</p> <p>Antwort-QR für den Host:</p>
<div id="qr-answer"></div> <div id="qr-answer"></div>
<p class="hint">Zeige diesen QR-Code dem Host</p> <p class="hint">Zeige diesen QR-Code dem Host</p>
<details class="debug-helper debug-only" style="margin-top:8px">
<summary>QR-Rohdaten</summary>
<pre id="answer-raw" class="raw-data"></pre>
</details>
</div> </div>
<ul id="join-player-list" class="player-list"></ul> <ul id="join-player-list" class="player-list"></ul>
</div> </div>
@ -121,9 +125,9 @@
</div> </div>
</section> </section>
<script src="https://unpkg.com/simple-peer@9.11.1/simplepeer.min.js"></script> <script src="vendor/simplepeer.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js"></script> <script src="vendor/qrcode.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jsqr@1.4.0/dist/jsQR.js"></script> <script src="vendor/jsQR.js"></script>
<script src="crypto.js"></script> <script src="crypto.js"></script>
<script src="p2p.js"></script> <script src="p2p.js"></script>
<script src="protocol.js"></script> <script src="protocol.js"></script>