OBS Plugin
Summary
The Telemy OBS plugin is a native C++ OBS module (aegis-obs-plugin.dll) that collects streaming telemetry directly from OBS C APIs, drives a CEF-hosted React dock UI via JS injection, and communicates with the AWS relay control plane over WinHTTP HTTPS. The architecture consolidated from a multi-process design (Rust bridge + IPC named pipes + WebSocket + web dashboard) in v0.0.3 to an all-native single-DLL architecture in v0.0.4, eliminating the standalone Rust binary, the MessagePack IPC protocol over named pipes (aegis_cmd_v1/aegis_evt_v1), the obs-websocket connection, the HTTP/WebSocket server on port 7070, and the system tray icon. The plugin loads automatically with OBS and handles its full lifecycle including emergency relay teardown on exit via obs_module_unload() with a server-side 5-minute heartbeat TTL as a safety net for crashes.
The dock UI is a React 18 JSX application rendered inside OBS’s Chromium Embedded Framework (CEF) browser panel. It began as a monolithic 3060-line aegis-dock.jsx file in v0.0.3 and was split on 2026-03-04 into 12 focused modules under obs-plugin/dock/ (constants, utils, CSS generator, hooks, state hooks, and component groups for UI, scenes, encoders, and the main layout). The dock supports 7 OBS themes (Yami, Acri, Grey, Rachni, Classic, Light, and default) via an 8-slot palette contract (bg, surface, panel, text, textMuted, accent, border, scrollbar) extracted from Qt’s QApplication::palette() on the C++ side and merged over Yami Grey fallback defaults on the JS side. Live theme updates flow through the existing aegis:dock:state-changed event with no dock reload required. A density/font separation system (implemented 2026-03-20) decouples OBS Density from font scale: C++ emits a densityLevel integer, JS computes a densityScale multiplier (0.5—1.5), and all structural spacing references calc(var(--dp, 1) * Xpx) while font zoom remains independent.
Key dock features include: scene rules with per-rule bitrate thresholds and a 2-row expanded edit layout with hover tooltips; an Encoders & Uploads section showing per-output health via self-calibrating gradient bitrate bars (green >=85% of target, yellow 70—85%, red <70%) with encoder group headers and hidden output toggles; sparkline charts replacing solid bars (bezier curves on <canvas>, circular buffer up to 60 minutes, 5m/15m/30m/60m time range selector); sign-out confirmation flow; secret field auto-relock after 5s blur / 30s reveal; and a 10px minimum font size floor enforced across all components.
Timeline
- 2026-02-24: Dock JSX v0.0.3 full rewrite —
aegis-dock.jsxrestructured to match DockState bridge contract.useDockState()hook reads fromwindow.aegisDockNativewith event-driven updates + 2s polling fallback.useSimulatedState()provides demo data. SIM badge, EngineStateChips (6-state 3x2 grid), and terminology alignment applied (S0-S3 replaced with STUDIO/IRL_CONNECTING/IRL_ACTIVE/IRL_GRACE/DEGRADED/FATAL). - 2026-02-24: Dock bridge readiness report compiled — bridge layer assessed at ~70% wired. DockState field mapping produced. Gaps identified:
failover.state,connections.items[], bitrate thresholds, settings mutations. Recommended IPC v1.1 additions:engine_state,thresholds,effective_flags,set_mode/set_settingmessage types. - 2026-02-25: OBS theme integration designed and implemented — 8-slot palette contract defined.
OBS_YAMI_GREY_DEFAULTSconstant added.DOCK_CSSconverted togetDockCss(theme)function.THEMEderived viauseMemofromstate.theme. All structural chrome colors (backgrounds, text, borders, scrollbars) replaced withTHEME.*references. Semantic status colors (health, intent, engine state) kept hardcoded. - 2026-02-27: Scene rules UX polish — expanded edit row restructured from 5-column grid to 2-row flex layout for 320px width. Threshold help icon with hover tooltip added. Light-theme safe form input backgrounds via
var(--theme-panel, var(--theme-bg, #fff))fallback chain. - 2026-03-01: Encoders & Uploads section added —
OutputBar,EncoderGroupHeader,HiddenOutputsTogglecomponents. Per-output gradient bitrate bars with health-color coding relative to rolling max. Simulated multi-encode data (2 groups, 5 outputs, 2 hidden). Old MULTISTREAM OUTPUTS sub-card removed from Bitrate section. - 2026-03-02: v0.0.4 all-native C++ plugin designed — Rust bridge, IPC named pipes, web dashboard, system tray eliminated. New C++ components: MetricsCollector (OBS C API at 500ms tick), WinHTTP HTTPS client, RelayClient with heartbeat/watchdog, DPAPI Vault + JSON config manager. 7-phase implementation plan approved.
- 2026-03-04: Dock code split executed — 3060-line monolith split into 12 modules under
obs-plugin/dock/. Root-level copies and sync script deleted. esbuild bundles from new location.TELEMY_DOCK_BRIDGE_ROOTenv var updated. - 2026-03-20: Density/font-scale separation — C++
dock_theme.cppdecoupledobsDensityfromfontSizePx. NewdensityLevelfield inObsDockThemeSlotsand JSON theme payload. JS computesdensityScale(each unit = +/-10% spacing, clamped 0.5—1.5), exposed as--dpCSS variable. All structural spacing inui-components.jsxandtelemy-dock.jsxupdated tocalc(var(--dp,1)*Xpx). - 2026-03-23: Sparkline output bars designed — solid bitrate bars replaced with smooth bezier sparkline charts per output. Canvas rendering with color zones relative to target bitrate. Circular buffer (60 min, 500ms sampling), time range selector (5m/15m/30m/60m). C++ change:
obs_encoder_get_settings(enc)→target_bitrate_kbpsper output. - 2026-03-23: Dock UX & theme fixes —
textMutedstandardized to#999999across 5 themes. Grey theme accent changed from invisible#4D4D4Dto#2D7AEDblue, panel/surface collision fixed. Acri surface darkened, accent corrected. Rachni scrollbar changed from pink to neutral grey. 10px minimum font size enforced (raised from 6-8px across StatPill, SecretField, EngineStateChips, SIM badge, footer, provision text). BYOR/MGD jargon replaced with Self-hosted/Managed. Tooltips added to SIM badge, IPC status, sparkline canvas. Failover/Output Config/Event Log sections default-collapsed. Sign-out confirmation step added. Secret field auto-relock (5s blur, 30s reveal). UpgradeBanner toned down from purple gradient to subdued accent border. GraceWarningBanner changed from solid red to amber warning strip.
Current State
The plugin is a single native C++ DLL (aegis-obs-plugin.dll) deployed to C:\Program Files (x86)\obs-studio\obs-plugins\64bit\. The dock source lives in telemy-v0.0.5/obs-plugin/dock/ as the single source of truth with 12 module files. Two JS bundles are built: aegis-dock-app.js for OBS CEF and telemy-dock.js for the Cloudflare Pages webpage demo. The C++ side collects per-output metrics at 500ms via obs_enum_outputs(), extracts target_bitrate_kbps from encoder settings, reads Qt palette + density level for theme injection, and manages relay lifecycle over WinHTTP. The dock renders 7 OBS themes live, supports density-aware spacing (Classic at 70% through Comfortable at 120%), enforces 10px minimum font size, and shows sparkline bitrate charts with self-calibrating color zones. Secret fields auto-relock. Scene rules use a 2-row expanded edit layout with threshold tooltips. The Encoders & Uploads section supports multi-encode setups with grouped outputs and hidden output toggles.
Key Decisions
- 2026-02-24: Bridge state shape uses exact DockState projection contract (
header,live,scenes,connections,bitrate,relay,failover,settings,events,pipe) — ensures dock JSX is a passive view decoupled from IPC implementation details. - 2026-02-25: Semantic status colors (health, intent, engine state) are NOT themed — universal signal colors must remain consistent across all OBS themes for safety-critical indicators.
- 2026-02-25: Yami Grey chosen as fallback palette — matches OBS default dark theme, ensures dock looks correct even without bridge-side theme injection.
- 2026-03-01: Per-output health bars use rolling max observed bitrate as self-calibrating target — avoids requiring user configuration, works correctly for any bitrate (6 Mbps TikTok and 40 Mbps streams both show green at their respective targets).
- 2026-03-02: WinHTTP chosen over libcurl for HTTPS — zero external DLL dependency, built into Windows, TLS via SChannel.
- 2026-03-02: JSON chosen over TOML for config storage (
%APPDATA%/Telemy/config.json) — Qt hasQJsonDocumentbuilt-in, no TOML parser needed. - 2026-03-02: Grafana OTLP export deferred from v0.0.4 — core focus is native plugin + dock + relay.
- 2026-03-02: 30s heartbeat / 5min TTL for relay session safety net — balances fast orphan cleanup with tolerance for temporary network blips.
- 2026-03-04: Relay section rendering kept inline in
aegis-dock.jsxduring code split — extracting it would require threading 15+ props; YAGNI applied. - 2026-03-20: Density and font scale decoupled —
fontSizePxreflects only font point-to-pixel conversion;densityLevelis the raw OBS Density integer. Each density unit = +/-10% spacing multiplier, clamped 0.5—1.5. - 2026-03-23: Sparkline target bitrate sourced from
obs_encoder_get_settings(enc)→"bitrate"field — works with StreamElements, Aitum, obs-multi-rtmp since all create standard OBS outputs with their own encoders. Fallback: rolling average when no encoder settings available. - 2026-03-23: 10px minimum font size floor enforced project-wide — nothing renders below 10px, key status info at >=12px.
Experiments & Results
| Experiment | Status | Finding | Source |
|---|---|---|---|
| IPC named pipe protocol (v0.0.3 Rust bridge) | Eliminated in v0.0.4 | Rust bridge was a middleman — plugin had direct OBS API access but serialized over pipes to Rust which connected back via WebSocket | v004-native-cpp-plugin-design |
| Named pipe ACL security for theme persistence | Superseded | Bridge built pipes with explicit user ACL to fix err=5 retries; moot after Rust bridge elimination | OBS_DOCK_THEME_PERSISTENCE |
| 5-column grid for scene rule expanded edit | Replaced | Too cramped at 320px dock width; switched to 2-row flex layout | scene-rules-ux-polish-design |
| Solid fill bitrate bars for outputs | Replaced | Replaced with smooth bezier sparkline charts for richer temporal context | sparkline-output-bars-design |
Single fontSizePx combining font scale + density | Replaced | Density and font scale must be independent; density affects padding/gap, font scale affects text zoom | density-font-separation |
| Purple gradient UpgradeBanner | Replaced | Toned down to subdued surface background with accent border-left to match OBS palette | dock-ux-theme-fixes |
| Solid red GraceWarningBanner | Replaced | Changed to amber warning strip to reduce visual dominance | dock-ux-theme-fixes |
Gotchas & Known Issues
- CEF cache: OBS CEF caches dock assets aggressively. If the dock shows a black screen after DLL update, force-close OBS and reopen.
- OBS restart required: The DLL is loaded at OBS startup; updating the DLL requires closing OBS, copying the new file, and relaunching.
- OBS non-default install path: OBS is installed at
C:\Program Files (x86)\obs-studio(not the default path). DLL deploy path isobs-plugins\64bit\telemy-obs-plugin.dll. TELEMY_DOCK_BRIDGE_ROOTenv var: Must point toE:\Code\telemyapp\telemy-v0.0.5\obs-plugin\dockfor the C++ dock host to find assets at dev time. OBS must be restarted after changing it.- Pipe status concept eliminated: In v0.0.4+, the plugin IS the bridge.
EmitDockNativePipeStatus()always emits"ok". The legacy IPCpipe.statusderivation (connected/degraded/down from heartbeat) no longer applies. - GPU metrics graceful degradation: NVML is dynamically loaded (
LoadLibraryW("nvml.dll")). If not present (non-NVIDIA GPU),gpu_percentandgpu_temp_care -1.0. No compile-time dependency. - Emergency relay stop blocks:
obs_module_unload()callsEmergencyRelayStop()which blocks up to 3s via WinHTTP timeout. This is intentional to prevent orphaned relay sessions. - Sparkline buffer always stores 60 minutes: The time range selector (5m/15m/30m/60m) only controls the visible window; the circular buffer always retains the full hour.
obs_output_get_video_encoder()returns non-owning pointer: Do NOT callobs_encoder_release()on the result inmetrics_collector.cpp.- Sign-out confirmation resets on section collapse: The
confirmLogoutstate lives in the AegisDock component; collapsing the Relay section does not persist the confirmation prompt.
Open Questions
- Per-link relay sparklines: currently out of scope, existing bars kept for relay connections. Will sparklines extend to relay links?
- Hover tooltips on sparkline: explicitly scoped out in the sparkline design. Will they be added later for precise value readout?
- Grafana OTLP export: deferred from v0.0.4. What triggers its implementation — user demand or a specific phase milestone?
- Config-defined target bitrates: sparklines currently use rolling average fallback when
obs_encoder_get_settingsis unavailable. Will a user config override be added? - Connection telemetry (
connections.items[]): still a gap from v0.0.3. Per-link network telemetry (SIM/WiFi/5G signal, per-link bitrate) requires local radio/network collector not yet implemented.
Sources
- 2026-03-23-dock-ux-theme-fixes.md
- 2026-03-23-sparkline-output-bars-design.md
- 2026-03-20-density-font-separation.md
- 2026-02-24-aegis-dock-jsx-v003-design.md
- 2026-02-24-dock-bridge-readiness-for-codex.md
- 2026-02-25-obs-theme-integration-design.md
- 2026-02-25-obs-theme-integration.md
- 2026-02-27-scene-rules-ux-polish-design.md
- 2026-02-27-scene-rules-ux-polish.md
- 2026-03-01-encoders-uploads-section-design.md
- 2026-03-01-encoders-uploads-plan.md
- 2026-03-04-dock-code-split-design.md
- 2026-03-04-dock-code-split-plan.md
- 2026-03-02-v004-native-cpp-plugin-design.md
- 2026-03-02-v004-native-cpp-implementation.md
- DOCK_TERMINOLOGY_ALIGNMENT_v0.0.3.md
- OBS_DOCK_THEME_PERSISTENCE.md
- IPC_PROTOCOL_v1.md