Hub: der Dienst laeuft zentral statt auf dem Stream-PC
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>
This commit is contained in:
1 parent
e18336c57f
commit
3b606e36fb
8 files changed
+985
-10
No files matched your search
@@ -45,6 +45,37 @@ export type Config = {
|
||||
ffmpeg: { encoder: string; preset: string; crf: number; niceness: number };
|
||||
whisper: { model: string; language: string; threads: number };
|
||||
};
|
||||
broadcaster: {
|
||||
enabled: boolean;
|
||||
/** `live` postet in die Ankuendigungen, `draft` in den internen Kanal. */
|
||||
mode: 'live' | 'draft';
|
||||
delaySecondsAfterOnline: number;
|
||||
cooldownHours: number;
|
||||
/** Rollenname aus dem Discord-Bauplan. Leer heisst: Post ohne Ping. */
|
||||
pingRole: string;
|
||||
obsScreenshot: boolean;
|
||||
};
|
||||
/** Kanalpunkt-Belohnungen und was sie ausloesen. Siehe apps/hub/src/rewards.ts. */
|
||||
rewards: {
|
||||
enabled: boolean;
|
||||
belohnungen: Array<{
|
||||
titel?: string; id?: string;
|
||||
aktion: 'konfetti' | 'emote-regen' | 'einblendung' | 'tts' | 'nichts';
|
||||
freigabe?: boolean; maxZeichen?: number;
|
||||
}>;
|
||||
tts: {
|
||||
freigabe: boolean; maxZeichen: number;
|
||||
blockliste: string[]; proPersonProStunde: number;
|
||||
};
|
||||
};
|
||||
overlay: { port: number; enabled: boolean };
|
||||
/**
|
||||
* Oeffentliche Adresse des Dienstes, ohne Schraegstrich am Ende. Nur noetig,
|
||||
* wenn er nicht auf demselben Rechner wie der Browser laeuft - dann haengt
|
||||
* daran der OAuth-Rueckruf von Twitch, der exakt zur eingetragenen
|
||||
* Redirect-URI passen muss.
|
||||
*/
|
||||
publicUrl: string;
|
||||
ingestServer: { port: number };
|
||||
dashboard: { port: number };
|
||||
raw: YamlMap;
|
||||
@@ -81,6 +112,40 @@ const DEFAULTS = {
|
||||
ffmpeg: { encoder: 'libx264', preset: 'veryfast', crf: 20, niceness: 15 },
|
||||
whisper: { model: 'ggml-small.bin', language: 'de', threads: 4 },
|
||||
},
|
||||
broadcaster: {
|
||||
enabled: true,
|
||||
// Vorgabe ist `live`: der Sinn der Sache ist der Ping. Wer erst zusehen
|
||||
// will, stellt `draft` ein - dann landet dieselbe Nachricht ohne Ping im
|
||||
// internen Kanal. Zum Ausprobieren gibt es `abdulive golive:test`.
|
||||
mode: 'live' as const,
|
||||
// Twitch setzt Titel und Kategorie erst kurz nach dem Start. Wer sofort
|
||||
// postet, kuendigt regelmaessig den Titel des letzten Streams an.
|
||||
delaySecondsAfterOnline: 60,
|
||||
cooldownHours: 6,
|
||||
pingRole: 'Live-Ping',
|
||||
obsScreenshot: true,
|
||||
},
|
||||
rewards: {
|
||||
// Aus, bis jemand bewusst Belohnungen eintraegt. Eine Aktion, die
|
||||
// niemand erwartet, ist im Stream schlimmer als gar keine.
|
||||
enabled: false,
|
||||
belohnungen: [] as Array<{
|
||||
titel?: string; id?: string;
|
||||
aktion: 'konfetti' | 'emote-regen' | 'einblendung' | 'tts' | 'nichts';
|
||||
freigabe?: boolean; maxZeichen?: number;
|
||||
}>,
|
||||
tts: {
|
||||
// Vorgabe ist die Freigabe durch Abdu. Kanalpunkte sammeln sich beim
|
||||
// Zugucken von allein an und sind damit kaum eine Huerde; ohne diesen
|
||||
// Schalter hat man eine Vorlesefunktion fuer Fremde im eigenen Stream.
|
||||
freigabe: true,
|
||||
maxZeichen: 200,
|
||||
blockliste: [] as string[],
|
||||
proPersonProStunde: 3,
|
||||
},
|
||||
},
|
||||
overlay: { port: 4791, enabled: true },
|
||||
publicUrl: '',
|
||||
ingestServer: { port: 4790 },
|
||||
dashboard: { port: 4792 },
|
||||
};
|
||||
@@ -105,7 +170,12 @@ export function loadConfig(): Config {
|
||||
if (fs.existsSync(file)) {
|
||||
raw = parseYaml(fs.readFileSync(file, 'utf8'));
|
||||
}
|
||||
cached = { ...merge(DEFAULTS, raw), raw } as Config;
|
||||
const merged = merge(DEFAULTS, raw) as Omit<Config, 'raw'>;
|
||||
// Die Umgebung gewinnt: im Container steht die oeffentliche Adresse in der
|
||||
// Unit-Datei, nicht in einer YAML, die mitversioniert wird.
|
||||
if (process.env.PUBLIC_URL) merged.publicUrl = process.env.PUBLIC_URL;
|
||||
merged.publicUrl = merged.publicUrl.replace(/\/+$/, '');
|
||||
cached = { ...merged, raw } as Config;
|
||||
return cached;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user