init: initial commit
- ticker / QR ad overlay / break slide graphics (1920×1080) with all geometry derived from one shared module (src/shared/obs-geometry.mjs) - OBS scene-collection generator (scripts/generate-obs-scenes.mjs) and a read-only in-dashboard setup verifier, both driven by the same module - en/sr dashboard i18n selected via bundle config; configschema.json with neutral first-run defaults seeded from optional branding config - install.sh / systemd user service / update.sh for venue deployment - reference OBS scene collection + profile from a real venue as fixtures
This commit is contained in:
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
export type Corner = "tr" | "bl";
|
||||
|
||||
export interface Ad {
|
||||
id: string;
|
||||
/** headline shown in the wide strip */
|
||||
rowText: string;
|
||||
/** text shown in the tall column */
|
||||
columnText: string;
|
||||
/** asset URL of the image shown in the column, null = no image */
|
||||
columnImageUrl: string | null;
|
||||
/** URL encoded into the QR code */
|
||||
qrUrl: string;
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
export interface AdState {
|
||||
visible: boolean;
|
||||
corner: Corner;
|
||||
adId: string | null;
|
||||
}
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
/** One file in a NodeCG `assets:<category>` replicant. */
|
||||
export interface AssetFile {
|
||||
base: string;
|
||||
category: string;
|
||||
ext: string;
|
||||
name: string;
|
||||
sum: string;
|
||||
url: string;
|
||||
}
|
||||
Vendored
+57
@@ -0,0 +1,57 @@
|
||||
export interface ObsSceneNames {
|
||||
main: string;
|
||||
mainAdTr: string;
|
||||
mainAdBl: string;
|
||||
break: string;
|
||||
breakAdTr: string;
|
||||
breakAdBl: string;
|
||||
}
|
||||
|
||||
export interface ObsTransitionNames {
|
||||
/** transition selected before ad shrink/hop switches (the Move transition) */
|
||||
default: string;
|
||||
/** transition selected before MAIN↔BREAK toggles (the stinger) */
|
||||
break: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Names of the shared OBS sources/nested scenes, for the setup verifier and
|
||||
* scene generator. Defaults live in src/shared/obs-geometry.mjs
|
||||
* (DEFAULT_SOURCE_NAMES); override here when an existing OBS setup uses
|
||||
* different names.
|
||||
*/
|
||||
export interface ObsSourceNames {
|
||||
adOverlay: string;
|
||||
ticker: string;
|
||||
breakSlide: string;
|
||||
programScene: string;
|
||||
breakScene: string;
|
||||
programPlaceholder: string;
|
||||
}
|
||||
|
||||
export interface ObsConnectionConfig {
|
||||
enabled: boolean;
|
||||
url: string;
|
||||
password: string;
|
||||
scenes: ObsSceneNames;
|
||||
transitions: ObsTransitionNames;
|
||||
sources: ObsSourceNames;
|
||||
}
|
||||
|
||||
/** First-run replicant seeds — only applied when nothing is persisted yet. */
|
||||
export interface BrandingConfig {
|
||||
tickerBlurb?: string;
|
||||
breakHeading?: string;
|
||||
sampleQrUrl?: string;
|
||||
}
|
||||
|
||||
export interface BundleConfig {
|
||||
/** dashboard language; defaults to "en" */
|
||||
locale?: string;
|
||||
branding?: BrandingConfig;
|
||||
obs?: Partial<ObsConnectionConfig> & {
|
||||
scenes?: Partial<ObsSceneNames>;
|
||||
transitions?: Partial<ObsTransitionNames>;
|
||||
sources?: Partial<ObsSourceNames>;
|
||||
};
|
||||
}
|
||||
Vendored
+32
@@ -0,0 +1,32 @@
|
||||
import type { Corner } from "./ads";
|
||||
|
||||
export interface ChoreoConfig {
|
||||
normalAdIntervalMinutes: number;
|
||||
adDurationSeconds: number;
|
||||
/** pause between ad bursts while in break mode */
|
||||
breakGapSeconds: number;
|
||||
breakAdDurationSeconds: number;
|
||||
breakAdsPerBurst: number;
|
||||
}
|
||||
|
||||
export interface SchedulerState {
|
||||
mode: "normal" | "break";
|
||||
phase: "live" | "ad";
|
||||
lastCorner: Corner;
|
||||
lastAdIndex: number;
|
||||
burstRemaining: number;
|
||||
/** epoch ms of the next automatic transition, for dashboard countdown */
|
||||
nextTransitionAt: number | null;
|
||||
}
|
||||
|
||||
export interface BreakSlide {
|
||||
heading: string;
|
||||
nextActName: string;
|
||||
nextActFrom: string;
|
||||
/** invert the band logo's colors (dark logo on the dark background) */
|
||||
logoInverted: boolean;
|
||||
/** `base` of the assets:break-logo file to show; null = first uploaded */
|
||||
logoName: string | null;
|
||||
/** rendered logo height in px (width follows the aspect ratio) */
|
||||
logoHeightPx: number;
|
||||
}
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
export * from "./ticker";
|
||||
export * from "./ads";
|
||||
export * from "./assets";
|
||||
export * from "./choreo";
|
||||
export * from "./obs";
|
||||
export * from "./bundleConfig";
|
||||
Vendored
+25
@@ -0,0 +1,25 @@
|
||||
export interface ObsStatus {
|
||||
connected: boolean;
|
||||
currentScene: string | null;
|
||||
lastError: string | null;
|
||||
attempts: number;
|
||||
}
|
||||
|
||||
export type ObsCheckStatus = "pass" | "warn" | "fail" | "skip";
|
||||
|
||||
export interface ObsCheckResult {
|
||||
id: string;
|
||||
label: string;
|
||||
status: ObsCheckStatus;
|
||||
/** what was actually found (shown under the label) */
|
||||
detail?: string;
|
||||
/** human instructions for making the check pass */
|
||||
fix?: string;
|
||||
}
|
||||
|
||||
export interface ObsVerifyReport {
|
||||
/** epoch ms of the last run, null before the first run */
|
||||
ranAt: number | null;
|
||||
running: boolean;
|
||||
checks: ObsCheckResult[];
|
||||
}
|
||||
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
export interface TickerMessage {
|
||||
id: string;
|
||||
text: string;
|
||||
/** epoch ms when the message was added (or last refreshed) */
|
||||
addedAt: number;
|
||||
/** null = permanent message, never swept */
|
||||
ttlSeconds: number | null;
|
||||
}
|
||||
|
||||
export interface TickerConfig {
|
||||
/** fixed blurb shown at every loop point */
|
||||
loopBlurb: string;
|
||||
separator: string;
|
||||
defaultTtlSeconds: number;
|
||||
speedPxPerSec: number;
|
||||
}
|
||||
Reference in New Issue
Block a user