This is the native Stream Deck plugin implementation workspace. For everyday setup, start with the plain-language guide. For screenshots, the supported-action matrix, and install notes, see the repo README.
Current positioning: early native prototype/MVP. It is not yet a full replacement for the Bitfocus Companion VDO.Ninja module because presets, named connections, and broader dynamic feedback are still in progress.
Every action targets a VDO.Ninja page opened with &api=KEY.
| Action | Controller | What it does |
|---|---|---|
| Connection Status | Key | Connection and status feedback for the configured API key. |
| Local Control | Key | Mic, camera, speaker, record, screen share, hand, keyframe, reload, and hangup on the local page. Mic also supports push-to-talk and push-to-mute. |
| Select Guest | Key | Selects a fixed slot/stream, the next or previous guest, the first held guest, or clears the selection. |
| Guest Command | Key | Guest control targeted by slot, stream ID, selected guest, or first held guest. |
| Guest Scene | Key | Arbitrary scene ID/name toggles, fixed-scene force on/off, and scene membership feedback. |
| Mixer Control | Key | Layout selection, guest slot assignment, all-guest mute, and a guarded all-guest transfer. |
| PTZ Key | Key | Local zoom/pan/tilt/focus/exposure and guest zoom/pan/tilt/focus/autofocus. |
| PTZ Dial | Stream Deck + | Local or guest zoom/pan/tilt/focus, local exposure, and guest autofocus press actions. |
| Value Dial | Stream Deck + | Local volume, panning, bitrate, buffer delay, and guest volume. |
| Custom Command | Key | Any { action, target, value, value2 } payload. |
Both dial actions support selected-target mode, inversion, optional acceleration, and rate-limited sends.
Beyond the actions themselves:
getDetails and getGuestList.{slot}, {label}, {streamID}, {command}, {scene}, and {state} tokens where relevant.npm install
npm run build
The build output is ninja.vdo.streamdeck.sdPlugin/.
Connection Status action in Stream Deck.Test connection.Local Control, Select Guest, Guest Command, Guest Scene, Mixer Control, PTZ Key, PTZ Dial, Value Dial, or Custom Command actions.The manifest targets the Node 20 runtime bundled with Stream Deck 6.8+; newer local Node versions work for development.
npm test
npm run check
npm run build
npx @elgato/cli@1.7.4 validate ninja.vdo.streamdeck.sdPlugin --no-update-check
npx @elgato/cli@1.7.4 pack ninja.vdo.streamdeck.sdPlugin --dry-run -f --no-update-check
pack refuses to run when a .streamDeckPlugin file from an earlier build is already there, so -f keeps the dry run repeatable.
These verify command payloads, TypeScript, generated plugin layout, manifest rules, and package contents. Tests cover every exposed command choice, property-inspector buttons and registry alignment, manifest image wiring, custom value parsing, transport behavior, and state normalization. Interactive button/dial testing still requires the Stream Deck app with either hardware or Stream Deck Mobile.
All artwork is generated from one spec by npm run assets, so the vector and raster forms cannot drift apart:
scripts/icon-set.mjs holds the palette and the shape list for every icon.scripts/generate-icons.mjs renders each spec to imgs/actions/*.svg (action list), imgs/*.svg (vector source), and anti-aliased imgs/*.png plus native @2x files (keys, encoders, marketplace tile).Edit the spec, not the output. Keypad state images keep their glyph in a corner badge so the two-line title the plugin draws stays legible. src/manifest-assets.test.ts fails the build if the manifest references an image that does not exist, if a raster image is the wrong size, or if a generated image is never referenced.
getDetails callbacks without a target are treated as full snapshots; details updates are treated as partial updates and merged.getDetails is also polled through the documented HTTP API route, using the configured interval as a backstop for DOM-derived state.getGuestList is treated as a director UI ordering helper, not as the universal stream list.Guest Scene action and raw/custom commands.addScene toggle, and alerts when that state is unavailable.layout=0 is auto, layout=1 is the first configured layout, and setslot uses user-facing destination slot numbers where 1 is mixer slot 1 and 0 unsets the assignment. Layout object fields such as slot: 0 keep VDO.Ninja’s existing zero-based layout-item convention.&slotmode=1 or use /mixer?director=ROOM&api=KEY; current VDO.Ninja reports the local page’s slotmode flag in getDetails, and the inspector shows a setup hint when it is off.mic command, excluding directors and screen-share pseudo-guests. This keeps the action usable with pre-v30.1 pages instead of depending on the newer muteAllGuests wrapper.forward commands one guest at a time and requires a second press by default.zoom/pan/tilt/focus/exposure, guest ptzZoom/ptzPan/ptzTilt/ptzFocus/ptzAutofocus. Guest exposure and local autofocus are intentionally blocked.&ptz and approve browser PTZ permission. Guest PTZ requires the guest publisher to load with &ptz; director/mixer pages can then send guest ptz* commands. Current VDO.Ninja reports the local page’s ptz flag in getDetails.volume, panning, bitrate, setBufferDelay, and guest volume. Buffer delay uses value2: "*" for all current inbound streams and omits value2 for default/future streams.mic=true on key down and mic=false on key up; push-to-mute sends the inverse. The action uses sequence guards so stale async completions do not repaint the key after a newer release.value2 use raw WebSocket payloads so secondary values are preserved.failed and timeout are treated as errors.Activate Guest requires VDO.Ninja v30.2+. Every other current-only API dependency has a legacy-safe plugin fallback.See ../docs/runtime-comparison-audit.md for the current comparison against VDO.Ninja’s local signaling and callback paths.