The plugin marketplace: extend without forking
Nine built-in plugins and a framework that discovers them from the filesystem — no central registry to edit. How StreamHub lets features plug into both the backend and the dashboard.
Features that plug in, not fork in
StreamHub ships a small plugin framework so features can attach to both the backend and the dashboard with zero edits to any central registry. A plugin is just a file dropped into a known folder; the framework discovers it, lists it in the per-app Plugins marketplace, validates its config against a typed schema, and — when the plugin declares one — owns the lifecycle of its worker process.
Install is per app, and every plugin is enabled, configured and uninstalled independently.
The nine built-in plugins
| Plugin | What it does |
|---|---|
| Cockpit | Drag-and-drop CCTV grid of every live stream in the app. |
| Quality | Bandwidth + latency test as a green/amber/red traffic light. |
| Radio | Audio-only WebRTC radio: go on air, count listeners. |
| Video Streaming | Go live from webcam + mic, forward the room to RTMP. |
| Timestamp | A live CCTV-style date/time stamp on the player. |
| Watermark | A text watermark in a corner of the player. |
| Scheduled Live | A countdown cover that hides the stream until a premiere time. |
| YOLO | A Python worker runs object detection and posts boxes to the overlay. |
| Deface | A Python worker detects faces; the player blurs them client-side. |
How discovery works
- Backend — drop
plugin.meta.tsdefault-exportingdefinePlugin({...}). A registry service globs the filesystem and imports each manifest. That manifest is the source of truth for the plugin'sid,category(tool|processor|panel), UI slot (app-tab|panel|player-overlay), and itsconfigSchema— where every field has a default, so a fresh install is valid immediately. - Frontend — drop
index.tsxdefault-exporting aPluginModule; discovery picks it up viaimport.meta.glob.
Workers, owned for you
A plugin that sets needsWorker: true provides a pure worker.spawn(ctx) returning {command, args, env}. The framework then owns the whole process lifecycle — enabling starts it, disabling stops it, and start/stop/status/logs are exposed over the API. YOLO and Deface both spawn a Python process this way; the core never needs to know what the worker actually is.
Installing a plugin
curl -X POST $BASE/apps/live/plugins/cockpit/install \
-H "Authorization: Bearer $TOKEN"
curl -X PATCH $BASE/apps/live/plugins/cockpit \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"enabled": true}'
That is the whole extensibility story: features are files, discovery is automatic, and the marketplace is per app.