In OBS' Browser-Quelle kam `speechSynthesis` amerikanisch heraus: der
eingebaute Browser sah keine deutsche Stimme, und die Seite nahm die erste
beliebige. Welche Stimmen es gibt, haengt am Rechner, der streamt.
Jetzt spricht der Hub mit Piper (lokal im LXC, 200 Zeichen in 0,7 s). Die
WAV liegt kurz im Speicher, `/tts` holt sie unter /tts/audio/<id> mit dem
Overlay-Schluessel und spielt sie ueber <audio> ab - das laeuft sicher ueber
OBS' Tonspur. Der Browser springt nur noch mit einer deutschen Stimme ein,
sonst wird nur angezeigt. Stimme und Sprecher stehen in rewards.tts.
Dazu:
- `abdulive rewards:anlegen`: Belohnung ueber die API anlegen. Nur die App,
die sie angelegt hat, darf Einloesungen abschliessen oder erstatten.
- Einloesungen werden nach der Aktion als FULFILLED markiert, statt in
Twitchs Warteschlange liegen zu bleiben.
- Wortfilter normalisiert vorher: Leetspeak, Punkte dazwischen, gezogene
Buchstaben, Akzente. Kurze Eintraege nur am Wortanfang.
- Der Testknopf "Vorlesen" geht durch Piper und prueft so die echte Stimme.
- scripts/install-piper.sh richtet Piper und Stimme im LXC ein.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Stand 2026-09-17. Der Dienst laeuft seitdem zentral, der Go-Live-Ping geht
raus, und das Overlay steht.
- Neuer Abschnitt "Neu: der Dienst laeuft zentral" mit der Tabelle, was wo
laeuft, und dem Hinweis, dass ADR-0005 damit seine Hauptbegruendung
verliert.
- "Der Go-Live-Ping geht raus": Ablauf, der Fallstrick mit `allowed_mentions`,
und die drei Sperren gegen den doppelten Ping. Dazu ehrlich, dass er noch
nie von allein ausgeloest hat.
- "Das Overlay steht": fuenf Seiten, SSE statt WebSocket, und dass Follows
fehlen.
- Der Discord-Knopf haengt nicht mehr am Daemon - dafuer ein neuer offener
Punkt: der Bot darf nur an einer Stelle laufen, sonst antworten zwei
Prozesse auf denselben Klick.
- Redirect-URI der Twitch-App auf die oeffentliche Adresse umgestellt, mit
dem Hinweis, dass Twitch sie erst nach dem Login prueft und ein Test von
aussen darueber nichts sagt.
- 162 -> 264 Tests.
config.example.yaml dokumentiert broadcaster, rewards, overlay und publicUrl.
apps/hub bekommt ein README mit dem Schnitt zwischen Hub und Ingest.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
EventSub ueber WebSocket wurde in ADR-0003 beschlossen und nie gebaut. Jetzt
gebaut, weil Kanalpunkt-Einloesungen ueber IRC nur ankommen, wenn die
Belohnung eine Texteingabe verlangt - und selbst dann ohne Einloese-ID. Ohne
die ID lassen sich Punkte nicht zurueckgeben, wenn eine Aktion scheitert.
Zwei Eigenheiten des Protokolls, die im Code stehen:
- Ein Abonnement muss innerhalb von 10 Sekunden nach dem Willkommensgruss
stehen, sonst schliesst Twitch wieder.
- Bei `session_reconnect` wird erst die neue Verbindung aufgebaut und dann die
alte geschlossen. Andersherum faellt genau dazwischen eine Einloesung unter
den Tisch.
Belohnungen werden ueber ihren Titel zugeordnet, wahlweise ueber die stabile
ID; liegt beides vor, gewinnt die ID. Eine unbekannte Belohnung ist kein
Fehler - es darf welche geben, die das Toolkit nichts angehen.
Zum Vorlesen, und warum es eine Warteschlange hat: chat-control.md schliesst
TTS fuer Ausbaustufe 1 aus, und der Grund gilt. Kanalpunkte sammeln sich beim
Zugucken von allein an und sind damit kaum eine Huerde; ohne Freigabe hat man
eine Vorlesefunktion fuer Fremde im eigenen Stream. Vorgabe ist deshalb die
Freigabe von Hand, dazu Laengengrenze, Wortfilter, Drossel je Person und
Stunde und ein Panikschalter. Zu langer Text wird abgelehnt statt gekuerzt -
ein halb vorgelesener Satz ist schlimmer als gar keiner, weil niemand weiss,
ob der Rest Absicht war.
Gesprochen wird ueber `speechSynthesis`, im Browser eingebaut: kein
Cloud-Dienst, keine Kosten, kein Zuschauertext, der das Haus verlaesst. Ob
OBS' Browser-Quelle das kann, ist offen - CEF wird teils ohne Sprachausgabe
gebaut. Die Seite prueft es beim Start und blendet einen roten Hinweis ein,
statt still zu bleiben.
Kanalpunkte haben eine eigene Einblendung. Zwischendurch stand hier die des
Abos, womit eine eingeloeste Belohnung als "Neues Abo" im Bild gelandet waere -
eine Falschmeldung, die in der Aufzeichnung bleibt. Ein Test haelt das fest.
`rewards.enabled` ist aus, bis jemand bewusst Belohnungen eintraegt. Eine
Aktion, die niemand erwartet, ist im Stream schlimmer als gar keine.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Zwei Dinge, die Reichweite bringen sollen, hingen bisher daran, dass jemand
vor seinem Rechner sitzt: der Discord-Rollen-Knopf und die Go-Live-
Ankuendigung. Beide brauchen nur Internet.
Der Schnitt verlaeuft entlang einer Frage: braucht die Aufgabe eine Datei
oder ein Programm, das nur auf dem Stream-PC existiert?
Hub (LXC) Ingest (Abdus PC)
--------- -----------------
Chat, Zuschauerzahlen OBS-Wiedergabepuffer
Discord: Knopf und Go-Live !clip, Rendern, Untertitel
Overlay-Seiten fuer OBS
Beide schreiben in ihre eigene Datenbank. Eine gemeinsame waere eine
Netzwerkfreigabe fuer SQLite - der zuverlaessigste bekannte Weg zu einer
beschaedigten Datei.
Das kostet ADR-0005 seine Hauptbegruendung: "bindet nur auf 127.0.0.1" gilt
fuer den Hub nicht mehr. An die Stelle treten ein Passwort mit signiertem
Sitzungs-Cookie fuer alles, was ein Mensch bedient, und ein zufaelliger
Schluessel in der Adresse fuer die Browser-Sources. Der Schluessel ist kein
schoenes Verfahren - er steht in jedem OBS-Profil -, aber eine Browser-Source
kann kein Formular ausfuellen und keinen Login durchhalten.
`publicUrl` haengt der OAuth-Rueckruf daran. Twitch vergleicht die
Redirect-URI zeichengenau, und ein fehlender Schraegstrich sieht im Fehlertext
aus wie ein falsches Client-Secret.
Die Routen der Einrichtungsseite sind aus ingest/server.ts herausgeloest,
weil sie jetzt an zwei Servern haengen.
Begruendung und Konsequenzen in ADR-0008.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>