vdo-streamdeck

VDO.Ninja Stream Deck Plugin Build Plan

Planning date: 2026-06-30.

This is the concrete plan for a native Elgato Stream Deck plugin dedicated to VDO.Ninja. It assumes no changes outside vdo-streamdeck/ until Steve explicitly approves them.

Product Goal

Build a Marketplace-ready native Stream Deck plugin that controls VDO.Ninja pages opened with &api=KEY.

The plugin should:

Primary transport is VDO.Ninja’s &api WebSocket. Iframe/P2P controls stay documented as future/advanced mode unless the plugin later owns an embedded controller page.

No-Regression Release Gate

The current native plugin is an early MVP, not yet a full Companion replacement. Do not position it as Marketplace-ready until it passes the no-regression checklist in professional-parity-and-no-regression-review.md.

Minimum gate:

Onboarding Goal

The plugin should not assume users already know VDO.Ninja’s &api parameter. The connection action/property inspector should guide first-time setup:

See onboarding-and-configuration-review.md for the comparison against other Stream Deck plugin setup patterns.

Current SDK Assumptions

Use the official Elgato Stream Deck SDK with TypeScript/Node.

Current official docs checked:

Candidate plugin identity:

Field Candidate
Display name VDO.Ninja
Category Video
Identifier ninja.vdo.streamdeck

Do not lock the identifier until Marketplace submission rules are checked during packaging. Elgato plugin identifiers should be treated as effectively permanent once published.

User Modes

The plugin should be designed around four practical modes:

Mode User Main needs
Local / Guest A performer controlling their own VDO.Ninja page Mic, camera, speaker, hand, screen share, record, chat, local PTZ.
Director A producer running a room Guest target selection, guest mic/camera/display, scenes, groups, solo talk, transfer, hangup, stats.
Technical Operator Someone managing stream quality Bitrate, buffer delay, keyframes, refresh/recover, stats, connection health.
PTZ Operator Camera operator Guest/local PTZ keys, dials, presets, autofocus, invert axes, selected target.

Implementation Shape

Expected package layout after scaffolding:

vdo-streamdeck/
  plugin/
    package.json
    *.sdPlugin/
      manifest.json
      bin/
      imgs/
      ui/
      logs/
    src/
      plugin.ts
      api/
        vdo-client.ts
        command-registry.ts
        request-tracker.ts
        types.ts
      state/
        session-store.ts
        selectors.ts
      actions/
        connection-status.ts
        local-command.ts
        local-toggle.ts
        guest-command.ts
        guest-scene.ts
        selected-target.ts
        ptz-key.ts
        ptz-dial.ts
        value-dial.ts
        stats-display.ts
        custom-command.ts
      ui/
        property-inspector/
      test/
        fake-api-server.ts
        fixtures/

Keep the exact folder names aligned with the scaffolder output when implementation starts.

Core Runtime Modules

VdoClient

Responsibilities:

Command Registry

The registry is the source of truth for actions and property inspector choices.

Each command definition should include:

type VdoCommandDefinition = {
	id: string;
	action: string;
	category: "local" | "guest" | "scene" | "group" | "ptz" | "stats" | "utility";
	label: string;
	target: "none" | "optional" | "required" | "value2";
	valueType: "none" | "toggle" | "boolean" | "number" | "text" | "select" | "json";
	value2Type?: "none" | "boolean" | "number" | "text" | "select";
	min?: number;
	max?: number;
	step?: number;
	feedback?: boolean;
	dangerous?: boolean;
	dialFriendly?: boolean;
	requiresDirector?: boolean;
	notes?: string;
};

Important registry rules:

Session Store

State buckets:

Bucket Source
connection WebSocket lifecycle and command timeouts.
detailsRaw getDetails callbacks and update.action == "details".
guestList getGuestList callbacks and positionChange refreshes.
streamsById Normalized from getDetails.
slotsByPosition Normalized from getGuestList.
selectedTarget Local plugin setting per profile/connection.
statsByStream getStats polling for visible stats actions.
lastCommand Per-action command result, timeout, and error.

Derived flags:

Flag How to derive
localMicMuted details[local].muted or update.action == "muted".
localCameraMuted details[local].videoMuted or update.action == "videoMuted".
localSpeakerMuted details[local].speakerMuted or update.action == "speakerMuted".
guestAudioMutedEffective stream.muted plus stream.others["mute-guest"] when present.
guestVideoMutedEffective stream.videoMuted plus director hide/camera controls in others.
guestInScene(scene) stream.scenes[scene] === true.
guestHeldOrQueued stream.others["remove-queue"] === true.
guestHandRaised stream.others["hand-raised"] === true.
stale Last update or snapshot older than a configured threshold.

Polling defaults:

Poll Default
getDetails On connect, after join/leave/position/detail events, and every 2 seconds while feedback actions are visible.
getGuestList On connect, when property inspector opens, after positionChange, and every 5 seconds while target dropdowns are visible.
getStats Only while visible stats actions exist; default 2 to 5 seconds.
requestStats Manual diagnostics only.

Action Classes

Keep the manifest action list high-level. Use settings and the command registry to avoid one manifest action per VDO command.

1. Connection Status

Purpose:

Settings:

Feedback:

2. Local Toggle

Commands:

Settings:

Feedback:

Implementation status:

3. Local Command

Commands:

Settings:

4. Guest Command

Commands:

Target modes:

Feedback:

Implementation status:

5. Guest Scene

Commands:

Settings:

Feedback:

Implementation status:

6. Group / View Group

Commands:

Settings:

7. Selected Target

Purpose:

Settings:

Feedback:

Implementation status:

8. PTZ Key

Commands:

Settings:

Implementation status:

9. PTZ Dial

Dial rotation:

Dial press:

Touch strip:

Implementation status:

Default dial mappings:

Dial Stream Deck + Stream Deck + XL
1 Selected target volume Selected target volume
2 Zoom Zoom
3 Pan/Tilt cycle Pan
4 Focus/Autofocus Tilt
5 N/A Focus/Autofocus
6 N/A Exposure/bitrate/buffer

10. Value Dial

Commands:

Settings:

Implementation status:

11. Stats Display

Purpose:

Data:

Display ideas:

12. Custom Command

Purpose:

Settings:

Guardrails:

Default Profiles

Profiles should be included as importable examples. Users can customize after install.

Stream Deck Mini - 6 Keys

+------------+------------+------------+
| Connection | Mic        | Camera     |
+------------+------------+------------+
| Speaker    | Record/Hand| Panic Page |
+------------+------------+------------+

Recommended behavior:

Stream Deck / 15 Keys

+------------+------------+------------+------------+------------+
| Connect    | Mic        | Camera     | Speaker    | Record     |
+------------+------------+------------+------------+------------+
| Share      | Hand       | Layout 1   | Layout 2   | Stats      |
+------------+------------+------------+------------+------------+
| Select G1  | G1 Mic     | G1 Cam     | G1 Scene   | G1 Solo    |
+------------+------------+------------+------------+------------+

Alternative director profile:

Stream Deck Neo - 8 Keys

+------------+------------+------------+------------+
| Connect    | Mic        | Camera     | Speaker    |
+------------+------------+------------+------------+
| Record     | Share      | Select     | Scene      |
+------------+------------+------------+------------+

Use the infobar for connection status, selected guest label, and last command result when supported by SDK profile assets.

Stream Deck XL - 32 Keys

Director-first layout:

+----------+----------+----------+----------+----------+----------+----------+----------+
| Connect  | LocalMic | LocalCam | Speaker  | Record   | Layout1  | Layout2  | Stats    |
+----------+----------+----------+----------+----------+----------+----------+----------+
| G1 Label | G1 Mic   | G1 Cam   | G1 Scene | G1 Talk  | G1 Solo  | G1 Vol   | G1 Hang  |
+----------+----------+----------+----------+----------+----------+----------+----------+
| G2 Label | G2 Mic   | G2 Cam   | G2 Scene | G2 Talk  | G2 Solo  | G2 Vol   | G2 Hang  |
+----------+----------+----------+----------+----------+----------+----------+----------+
| Select   | Group    | ViewGrp  | Transfer | Keyframe | Refresh  | PTZ Page | Custom   |
+----------+----------+----------+----------+----------+----------+----------+----------+

Add folders/pages for:

Stream Deck + - 8 Keys + 4 Dials

Keys:

+------------+------------+------------+------------+
| Connect    | Mic        | Camera     | Record     |
+------------+------------+------------+------------+
| Select     | G Mic      | G Cam      | G Scene    |
+------------+------------+------------+------------+

Dials:

Dial Rotate Press Touch strip
1 Selected target volume Reset to 100% target + volume
2 Selected target zoom Toggle absolute/relative target + zoom
3 Pan/Tilt Cycle pan/tilt axis + step
4 Focus Toggle autofocus focus/autofocus

Stream Deck + XL - 36 Keys + 6 Dials

Use as the premium director/PTZ profile:

Stream Deck Pedal - 3 Footswitches

+------------+------------+------------+
| PTT/Mic    | Cough/Speak| Marker/Rec |
+------------+------------+------------+

Alternative director pedal:

Stream Deck Mobile / Virtual

Provide a key-only profile based on the 15-key profile. No dial-only features should be required for core workflows.

Property Inspector Plan

Global Settings

Fields:

Action Settings

Common fields:

Target modes:

Title template tokens:

Token Meaning
{label} Stream label.
{streamID} Stream ID.
{slot} Guest slot.
{state} Derived state label.
{scene} Scene ID/name.
{command} Command label.
{value} Configured value.
{lastResult} Last command result.

Feedback and Visual States

Use consistent colors across profiles:

State Color intent
Connected/on/active Green.
Muted/off/disabled Red or dark gray depending action.
Pending/connecting Yellow.
Error/rejected/timeout Red pulse or temporary warning icon.
Stale/missing target Gray with warning title.
Dangerous armed Orange/red until confirmed.

Icon groups:

Use generated or custom bitmap icons for Marketplace polish later. During implementation, start with simple SDK-compatible SVG/PNG assets inside the plugin folder.

Queue / Approval Plan

State:

Control:

This is a specific area where the native plugin can go beyond Companion while staying on the standard &api control path.

Implementation Milestones

Phase 1 - Scaffolding and Connection

Deliverables:

Acceptance:

Phase 2 - Local Controls MVP

Deliverables:

Commands:

Acceptance:

Phase 3 - Director Guest Controls

Deliverables:

Commands:

Acceptance:

Phase 4 - PTZ and Dials

Deliverables:

Commands:

Acceptance:

Phase 5 - Stats, Profiles, and Polish

Deliverables:

Acceptance:

Testing Plan

Automated:

Manual:

Marketplace smoke:

Deferred / Requires Permission

These are useful but should not block MVP:

Open Decisions

Decision Default recommendation
Multiple API keys in v1? No. Start with one global connection profile, design data model so multiple profiles can be added.
HTTP fallback? Yes, optional and off by default for command paths that need reliable feedback.
Custom command in Marketplace build? Yes, but with warnings and no unsafe raw WebSocket routing.
Queue activation in MVP? Display queued state only. Add control after a VDO API action exists or embedded mode is approved.
Stats polling default? Disabled unless a stats action is visible.
Dangerous command safety? Double-tap by default for hangup, guest hangup, reload, transfer, recover stream.

Immediate Next Work

  1. Add muteScene and group/view-group actions.
  2. Add default profiles/presets for 15-key, XL, Stream Deck +, and Pedal.
  3. Validate callbacks and live updates against a running VDO.Ninja page with room, push/view, scene, transfer-room, and PTZ-capable camera cases.
  4. Smoke test PTZ Dial behavior on physical Stream Deck + / + XL hardware with a PTZ-capable VDO.Ninja setup.
  5. Add richer stale-target and command-timeout visual states.

Completed implementation baseline: