/* ===================================================================== DIE SPENDENTAFEL ALS BROWSER-QUELLE (28.09.2026) Hört am Bühnenstrom und lässt jede bestätigte Spende als Karte laufen. Sonst tut diese Seite nichts. --------------------------------------------------------------------- SIE MUSS TAGE LAUFEN, OHNE DASS JEMAND HINSIEHT Das ist der ganze Unterschied zu einer Seite im Browser: Wer eine Seite offen hat, merkt, wenn sie hängt, und lädt neu. Eine Quelle in OBS läuft im Hintergrund, und wenn sie einmal stehenbleibt, fällt es erst auf, wenn die erste Spende nicht erscheint — mitten in der Sendung, vor Publikum. Deshalb drei Dinge: Der Browser verbindet einen abgerissenen Ereignisstrom von selbst neu (`retry:` kommt vom Server). Darauf allein zu bauen reicht aber nicht: Schläft der Rechner, kann der Strom in einem Zustand stehenbleiben, aus dem der Browser nicht von selbst herausfindet. Also eine eigene WACHE: Kommt 70 Sekunden lang gar nichts — auch kein Lebenszeichen, das alle 25 Sekunden gesendet wird —, wird die Verbindung weggeworfen und neu aufgebaut. Und ein FEHLER WIRD ANGEZEIGT, aber nur der, der etwas bedeutet: ein falscher Schlüssel. Alles andere bleibt still, weil jede Fläche hier im Stream zu sehen wäre. ===================================================================== */ (() => { 'use strict'; const tafel = document.getElementById('tafel'); /* ==== GROESSE UND LAGE STEHEN IN DER ADRESSE ==================== Wer in OBS eine Browser-Quelle einrichtet, hat die Adresse ohnehin vor sich. Ein Wert, den man stattdessen im Regiepult suchen muesste, waere ein Fensterwechsel mitten im Einrichten -- und beim Probieren aendert man zehnmal hintereinander etwas. DIE VORGABE IST 1,4 UND NICHT 1. Im Saal steht die Karte neben einem Chat und darf nichts zudecken; auf der Buehne deckt sie nichts zu, was man lesen muesste, und darf deutlich groesser sein. Eine Vorgabe, bei der man immer nachstellen muss, ist die falsche. */ { const p = new URLSearchParams(location.search); const g = Number(String(p.get('g') || '').replace(',', '.')); /* Zwischen 0,5 und 4. Darunter ist nichts mehr zu lesen, darueber passt keine Karte mehr ins Bild -- beides waere eine Quelle, die aussieht, als sei sie kaputt. */ const faktor = Number.isFinite(g) && g >= 0.5 && g <= 4 ? g : 1.4; document.documentElement.style.fontSize = `${Math.round(16 * faktor)}px`; const lagen = { ul: ['flex-end', 'flex-start'], ur: ['flex-end', 'flex-end'], ol: ['flex-start', 'flex-start'], or: ['flex-start', 'flex-end'], mitte: ['center', 'center'], }; const [senkrecht, waagerecht] = lagen[String(p.get('pos') || 'ul')] || lagen.ul; tafel.style.justifyContent = senkrecht; tafel.style.alignItems = waagerecht; } const schluessel = new URLSearchParams(location.search).get('s') || ''; function sagFehler(text) { let k = document.querySelector('.obs-fehler'); if (!k) { k = document.createElement('div'); k.className = 'obs-fehler'; document.body.append(k); } k.textContent = text; } if (!schluessel) { sagFehler('Diese Adresse ist unvollständig — der Schlüssel fehlt. ' + 'Hol sie dir im Regiepult unter „Bühne“.'); return; } let strom = null; let letztes = Date.now(); function verbinden() { try { if (strom) strom.close(); } catch { /* war schon zu */ } strom = new EventSource( `/workspace/api/buehne/strom?s=${encodeURIComponent(schluessel)}`); strom.addEventListener('open', () => { letztes = Date.now(); const k = document.querySelector('.obs-fehler'); if (k) k.remove(); }); /* JEDE NACHRICHT ZAEHLT ALS LEBENSZEICHEN -- auch die Doppelpunkt- Zeile, die der Server alle 25 Sekunden schickt. Sie kommt hier als „message" ohne Inhalt an. */ strom.addEventListener('message', () => { letztes = Date.now(); }); strom.addEventListener('spende', (e) => { letztes = Date.now(); try { const d = JSON.parse(e.data); if (d.was === 'zeigen' && window.Spendenkarte) { window.Spendenkarte.zeigen(tafel, d.karte); } } catch { /* eine kaputte Meldung darf die Quelle nicht reissen */ } }); strom.addEventListener('error', () => { /* KEINE MELDUNG INS BILD. Ein kurzer Abriss ist normal, und der Browser verbindet von selbst neu. Eine rote Fläche dafür stünde im Stream. */ if (strom.readyState === EventSource.CLOSED) { setTimeout(verbinden, 3000); } }); } /* Einmal fragen, ob der Schlüssel überhaupt stimmt. Eine Quelle, die still bleibt, weil die Adresse falsch ist, sieht genauso aus wie eine, bei der nur niemand gespendet hat -- und das ist die eine Verwechslung, die hier wirklich schadet. */ fetch(`/workspace/api/buehne/lage?s=${encodeURIComponent(schluessel)}`) .then((a) => { if (a.status === 404) { sagFehler('Der Schlüssel stimmt nicht mehr. ' + 'Hol dir die Adresse neu im Regiepult unter „Bühne“.'); return; } verbinden(); }) .catch(() => verbinden()); /* DIE WACHE. Kommt 70 Sekunden nichts -- nicht einmal ein Lebenszeichen --, ist die Verbindung tot, auch wenn der Browser sie fuer offen haelt. */ setInterval(() => { if (Date.now() - letztes > 70_000) { letztes = Date.now(); verbinden(); } }, 10_000); /* WACHT DER RECHNER AUF, WIRD SOFORT NEU VERBUNDEN. Nach dem Ruhezustand ist ein Ereignisstrom fast immer tot, und bis die Wache greift, vergeht eine Minute. */ document.addEventListener('visibilitychange', () => { if (!document.hidden && Date.now() - letztes > 30_000) verbinden(); }); })();