Claude: Phase 2 button

This commit is contained in:
chk
2026-06-04 21:29:35 +02:00
parent c0d9deacd9
commit 9433df15b1
2 changed files with 80 additions and 19 deletions

View File

@@ -351,19 +351,80 @@ function showNotice() {
bar.style.display = 'flex';
}
// ── Snapshot aller Kameras ───────────────────────────────────────────────────
function snapshotAll() {
const ts = Date.now();
const ids = cameras.map(c => c.id);
log('snap', `Snapshot alle: ${ids.join(', ')}`);
ids.forEach(id => {
const a = document.createElement('a');
a.href = `/api/snapshot/${id}`;
a.download = `${id}_${ts}.jpg`;
document.body.appendChild(a);
a.click();
a.remove();
});
// ── HD-Snapshot aller Kameras (parallel) ─────────────────────────────────────
// cam0 und cam1 liegen auf getrennten Geräten → gleichzeitiger Grab sicher.
// Alle Live-Streams werden synchron eingefroren und losgelassen, dann beide
// /hires-Requests parallel gefeuert. finally stellt immer alle zurück.
async function snapshotAllHires() {
if (cameras.some(c => c.testing)) return;
const snapBtn = document.getElementById('snapAllBtn');
if (snapBtn) snapBtn.disabled = true;
cameras.forEach(c => { c.testing = true; c.hdBtn.disabled = true; });
log('snap', `HD-Grab alle: ${cameras.map(c => c.id).join(', ')}`);
try {
// 1. Alle Freeze-Canvases gleichzeitig aufbauen (je ein /api/snapshot-Fetch)
await Promise.all(cameras.map(c => showFreezeCanvas(c, 'Capturing HD…')));
// 2. Alle Live-Streams synchron loslassen → alle Consumer fallen gleichzeitig auf 0
cameras.forEach(c => stopStream(c));
const ts = Date.now();
// 3. Alle /hires-Grabs parallel Fehler einer Kamera blockieren die andere nicht
await Promise.allSettled(cameras.map(async c => {
try {
const r = await fetch(
`/api/snapshot/${encodeURIComponent(c.id)}/hires`,
{ signal: AbortSignal.timeout(20000) }
);
if (!r.ok) {
const body = await r.json().catch(() => ({}));
throw new Error(body.error ?? `HTTP ${r.status}`);
}
const blob = await r.blob();
const blobUrl = URL.createObjectURL(blob);
if (c.freezeCanvas) {
const ctx = c.freezeCanvas.getContext('2d');
await new Promise(resolve => {
const img = new Image();
img.onload = () => { ctx.drawImage(img, 0, 0, 640, 480); resolve(); };
img.onerror = resolve;
img.src = blobUrl;
});
updateBadge(c, 'HD ✓', '#8f8');
}
const a = document.createElement('a');
a.href = blobUrl;
a.download = `${c.id}_hires_${ts}.jpg`;
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(blobUrl);
setInfo(c, 'HD gespeichert', 'ok');
log(c.id, `HD-Grab OK ${blob.size} bytes`);
} catch (e) {
logErr(c.id, 'HD-Grab fehlgeschlagen', e);
setInfo(c, `HD Fehler: ${e.message}`, 'crit');
}
}));
} finally {
// 4. Immer: alle zurück auf Live
await sleep(600);
cameras.forEach(c => {
removeFreezeCanvas(c);
startStream(c);
c.testing = false;
c.hdBtn.disabled = false;
});
if (snapBtn) snapBtn.disabled = false;
log('snap', '── HD-Grab alle beendet, alle zurück auf Live ──');
}
}
// ── Kamera-View aufbauen ─────────────────────────────────────────────────────
@@ -443,7 +504,7 @@ async function init() {
if (camIds.length === 0) { warn('init', 'Fallback cam0, cam1'); camIds = ['cam0', 'cam1']; }
const snapBtn = document.getElementById('snapAllBtn');
if (snapBtn) { snapBtn.onclick = snapshotAll; snapBtn.disabled = false; }
if (snapBtn) { snapBtn.onclick = snapshotAllHires; snapBtn.disabled = false; }
camIds.forEach(id => buildCamera(id, container));
statusText.textContent = `${camIds.length} Kamera${camIds.length !== 1 ? 's' : ''} · WebRTC`;