Claude Multicam (a)
This commit is contained in:
@@ -66,6 +66,12 @@
|
||||
}
|
||||
.cam-toggle:hover { background: rgba(60,60,60,.85); }
|
||||
|
||||
/* Snapshot-only-Kamera: Platzhalter statt Live-Bild */
|
||||
.cam-placeholder {
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
color: #444; font-size: 0.82rem; letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
/* Hi-Res-Test-Button (Phase 1) – links neben dem Ein/Aus-Schalter */
|
||||
.cam-hdtest {
|
||||
position: absolute; top: 5px; right: 40px; z-index: 2;
|
||||
|
||||
@@ -43,7 +43,7 @@ async function runHiresGrab(cam) {
|
||||
if (cam.busy) return;
|
||||
cam.busy = true;
|
||||
cam.hdBtn.disabled = true;
|
||||
setInfo(cam, 'HD: erfasse… (Stream friert kurz)', 'warn');
|
||||
setInfo(cam, cam.stream ? 'HD: erfasse… (Stream friert kurz)' : 'HD: erfasse…', 'warn');
|
||||
log(cam.id, '── HD-Grab gestartet ──');
|
||||
|
||||
let blobUrl = null;
|
||||
@@ -99,51 +99,64 @@ function setInfo(cam, text, cls) {
|
||||
}
|
||||
|
||||
// ── Kamera-View aufbauen ──────────────────────────────────────────────────────
|
||||
function buildCamera(camId, container) {
|
||||
// camMeta = { id, name, position, stream, hires }
|
||||
function buildCamera(camMeta, container) {
|
||||
const box = document.createElement('div');
|
||||
box.className = 'cam-box';
|
||||
|
||||
const img = document.createElement('img');
|
||||
img.className = 'cam-img';
|
||||
img.alt = camId;
|
||||
img.addEventListener('load', () => { if (cam.active && !cam.busy) setInfo(cam, 'MJPEG · live', 'ok'); });
|
||||
img.addEventListener('error', () => {
|
||||
if (!cam.active) return;
|
||||
setInfo(cam, 'Verbindungsfehler – neu…', 'crit');
|
||||
// Auto-Reconnect nach kurzer Pause (nicht während HD-Grab)
|
||||
setTimeout(() => { if (cam.active && !cam.busy) startStream(cam); }, 2000);
|
||||
});
|
||||
|
||||
const labelText = camMeta.name + (camMeta.position ? ` · ${camMeta.position}` : '');
|
||||
const label = document.createElement('div');
|
||||
label.className = 'cam-label';
|
||||
label.textContent = camId;
|
||||
label.textContent = labelText;
|
||||
|
||||
const info = document.createElement('div');
|
||||
info.className = 'cam-info';
|
||||
info.textContent = '…';
|
||||
|
||||
const toggle = document.createElement('button');
|
||||
toggle.className = 'cam-toggle';
|
||||
info.textContent = camMeta.stream ? '…' : 'Nur Snapshot';
|
||||
|
||||
const hd = document.createElement('button');
|
||||
hd.className = 'cam-hdtest';
|
||||
hd.textContent = 'HD';
|
||||
hd.title = 'Hi-Res-Snapshot (1280×960) – Live friert kurz ein, dann Download';
|
||||
|
||||
const cam = { id: camId, box, img, infoEl: info, toggleBtn: toggle, hdBtn: hd, active: false, busy: false };
|
||||
const cam = { id: camMeta.id, stream: camMeta.stream, box, infoEl: info, hdBtn: hd, active: false, busy: false };
|
||||
|
||||
toggle.onclick = () => { if (!cam.busy) (cam.active ? stopStream(cam) : startStream(cam)); };
|
||||
hd.onclick = () => runHiresGrab(cam);
|
||||
|
||||
box.appendChild(img);
|
||||
if (camMeta.stream) {
|
||||
const img = document.createElement('img');
|
||||
img.className = 'cam-img';
|
||||
img.alt = labelText;
|
||||
img.addEventListener('load', () => { if (cam.active && !cam.busy) setInfo(cam, 'MJPEG · live', 'ok'); });
|
||||
img.addEventListener('error', () => {
|
||||
if (!cam.active) return;
|
||||
setInfo(cam, 'Verbindungsfehler – neu…', 'crit');
|
||||
setTimeout(() => { if (cam.active && !cam.busy) startStream(cam); }, 2000);
|
||||
});
|
||||
|
||||
const toggle = document.createElement('button');
|
||||
toggle.className = 'cam-toggle';
|
||||
toggle.onclick = () => { if (!cam.busy) (cam.active ? stopStream(cam) : startStream(cam)); };
|
||||
|
||||
cam.img = img;
|
||||
cam.toggleBtn = toggle;
|
||||
hd.title = 'Hi-Res-Snapshot (1280×960) – Live friert kurz ein, dann Download';
|
||||
|
||||
box.appendChild(img);
|
||||
box.appendChild(toggle);
|
||||
startStream(cam);
|
||||
} else {
|
||||
const placeholder = document.createElement('div');
|
||||
placeholder.className = 'cam-img cam-placeholder';
|
||||
placeholder.textContent = 'Kein Live-Stream';
|
||||
hd.title = 'Hi-Res-Snapshot (1280×960) – Download';
|
||||
box.appendChild(placeholder);
|
||||
}
|
||||
|
||||
box.appendChild(label);
|
||||
box.appendChild(info);
|
||||
box.appendChild(toggle);
|
||||
box.appendChild(hd);
|
||||
container.appendChild(box);
|
||||
|
||||
cameras.push(cam);
|
||||
startStream(cam);
|
||||
}
|
||||
|
||||
// ── Init ──────────────────────────────────────────────────────────────────────
|
||||
@@ -152,22 +165,28 @@ async function init() {
|
||||
const container = document.getElementById('cameras');
|
||||
const statusText = document.getElementById('statusText');
|
||||
|
||||
let camIds = [];
|
||||
let camList = [];
|
||||
try {
|
||||
const r = await fetch('/api/snapshot');
|
||||
log('init', `/api/snapshot → HTTP ${r.status}`);
|
||||
if (r.ok) camIds = ((await r.json()).cameras ?? []).map((c) => c.id);
|
||||
log('init', `Kameras: ${camIds.join(', ') || '(keine)'}`);
|
||||
if (r.ok) camList = (await r.json()).cameras ?? [];
|
||||
log('init', `Kameras: ${camList.map((c) => c.id).join(', ') || '(keine)'}`);
|
||||
} catch (e) {
|
||||
logErr('init', '/api/snapshot Fehler – Fallback', e);
|
||||
}
|
||||
if (camIds.length === 0) { warn('init', 'Fallback cam0, cam1'); camIds = ['cam0', 'cam1']; }
|
||||
if (camList.length === 0) {
|
||||
warn('init', 'Fallback cam0, cam1');
|
||||
camList = [
|
||||
{ id: 'cam0', name: 'cam0', position: '', stream: true, hires: true },
|
||||
{ id: 'cam1', name: 'cam1', position: '', stream: true, hires: true },
|
||||
];
|
||||
}
|
||||
|
||||
const snapBtn = document.getElementById('snapAllBtn');
|
||||
if (snapBtn) { snapBtn.onclick = snapshotAllHires; snapBtn.disabled = false; }
|
||||
|
||||
camIds.forEach((id) => buildCamera(id, container));
|
||||
statusText.textContent = `${camIds.length} Kamera${camIds.length !== 1 ? 's' : ''} · MJPEG`;
|
||||
camList.forEach((c) => buildCamera(c, container));
|
||||
statusText.textContent = `${camList.length} Kamera${camList.length !== 1 ? 's' : ''} · MJPEG`;
|
||||
log('init', 'Fertig');
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user