Node.js bindings for NDI (Network Device Interface) SDK.
NDI is a royalty-free software specification developed by NewTek that enables video-compatible products to communicate, deliver, and receive high-quality video over IP networks.
- NDI Source Discovery - Find NDI sources on your network
- NDI Sender - Broadcast video and audio as an NDI source
- NDI Receiver - Receive video and audio from NDI sources
- PTZ Control - Control PTZ cameras over NDI
- Tally Support - Send and receive tally information
- Metadata - Exchange XML metadata with NDI sources
- Async/Await Support - Non-blocking async methods for all operations
- TypeScript Support - Full TypeScript type definitions included
You need to download and install the NDI SDK from NewTek:
- Visit https://ndi.video/download-ndi-sdk/
- Download the NDI SDK for your platform
- Copy the SDK files to the
deps/ndidirectory:
deps/ndi/
├── include/
│ └── Processing.NDI.Lib.h
└── lib/
└── x64/ (Windows)
├── Processing.NDI.Lib.x64.lib
└── Processing.NDI.Lib.x64.dll
- Windows: Visual Studio Build Tools with C++ workload
- macOS: Xcode Command Line Tools
- Linux: GCC/G++ and build-essential
npm install @vygr-labs/ndi-nodeOr with yarn:
yarn add @vygr-labs/ndi-nodeOr with pnpm:
pnpm add @vygr-labs/ndi-nodeThis will compile the native addon using node-gyp. After installation, a post-install script will check for the NDI SDK and provide setup instructions if needed.
const ndi = require('@vygr-labs/ndi-node');
async function main() {
// Initialize NDI
ndi.initialize();
// Create a finder
const finder = new ndi.Finder({ showLocalSources: true });
// Find sources asynchronously (non-blocking)
const sources = await finder.getSourcesAsync();
console.log('Found sources:', sources);
// Or wait for sources to appear
const result = await finder.waitForSourcesAsync(5000);
console.log('Sources changed:', result.changed);
console.log('Sources:', result.sources);
// Cleanup
finder.destroy();
ndi.destroy();
}
main();const ndi = require('@vygr-labs/ndi-node');
async function main() {
ndi.initialize();
const sender = new ndi.Sender({
name: 'My NDI Source',
clockVideo: true
});
// Send a frame asynchronously (non-blocking, runs on background thread)
await sender.sendVideoPromise({
xres: 1920,
yres: 1080,
fourCC: ndi.FourCC.BGRA,
frameRateN: 30000,
frameRateD: 1001,
data: frameBuffer // Buffer containing BGRA pixel data
});
// Check tally and connections asynchronously
const [tally, connections] = await Promise.all([
sender.getTallyAsync(),
sender.getConnectionsAsync()
]);
console.log('Tally:', tally);
console.log('Connections:', connections);
// Cleanup
sender.destroy();
ndi.destroy();
}
main();const ndi = require('@vygr-labs/ndi-node');
async function main() {
ndi.initialize();
// Find sources first
const finder = new ndi.Finder();
const result = await finder.waitForSourcesAsync(5000);
if (result.sources.length === 0) {
console.log('No sources found');
return;
}
// Create receiver and connect to first source
const receiver = new ndi.Receiver({
source: result.sources[0],
colorFormat: ndi.ColorFormat.BGRX_BGRA,
bandwidth: ndi.Bandwidth.HIGHEST
});
// Capture frames asynchronously (non-blocking)
while (true) {
const frame = await receiver.captureAsync(1000);
if (frame.type === 'video') {
console.log(`Video: ${frame.video.xres}x${frame.video.yres}`);
// frame.video.data contains the pixel buffer
} else if (frame.type === 'audio') {
console.log(`Audio: ${frame.audio.noSamples} samples`);
}
}
// Cleanup
receiver.destroy();
finder.destroy();
ndi.destroy();
}
main();Initialize the NDI library. Must be called before using any other functions.
Cleanup the NDI library. Should be called when done using NDI.
Get the NDI library version string.
Find NDI sources on the network.
new ndi.Finder(options?)Options:
showLocalSources: boolean- Include local sources (default: true)groups: string- Comma-separated list of groups to searchextraIps: string- Extra IPs to search for sources
Methods:
getSources(): Source[]- Get currently discovered sources (sync)getSourcesAsync(): Promise<Source[]>- Get sources asynchronously (non-blocking)waitForSources(timeout?): boolean- Wait for sources to change (sync)waitForSourcesAsync(timeout?): Promise<{changed, sources}>- Wait for sources asynchronously (non-blocking)startPolling(interval?)- Start polling for sourcesstopPolling()- Stop pollingdestroy()- Release resources
Events:
'sources'- Emitted when sources change (when using polling)
new ndi.Sender(options)Options:
name: string- Name of the NDI source (required)groups: string- Comma-separated list of groupsclockVideo: boolean- Clock video to frame rate (default: true)clockAudio: boolean- Clock audio to sample rate (default: true)
Methods:
sendVideo(frame)- Send a video frame (sync)sendVideoAsync(frame)- Send a video frame using NDI async APIsendVideoPromise(frame): Promise<void>- Send a video frame on background thread (non-blocking)sendAudio(frame)- Send an audio frame (sync)sendAudioPromise(frame): Promise<void>- Send an audio frame on background thread (non-blocking)sendMetadata(frame)- Send metadatagetTally(timeout?): Tally | null- Get tally state (sync)getTallyAsync(timeout?): Promise<Tally | null>- Get tally state asynchronously (non-blocking)setTally(tally)- Set tally stategetConnections(timeout?): number- Get number of connections (sync)getConnectionsAsync(timeout?): Promise<number>- Get connections asynchronously (non-blocking)getSourceName(): string | null- Get full source namestartTallyPolling(interval?)- Start polling for tally changesstopTallyPolling()- Stop tally pollingdestroy()- Release resources
Events:
'tally'- Emitted when tally state changes (when using polling)
new ndi.Receiver(options?)Options:
source: Source- Source to connect tocolorFormat: string- Color format (default: 'BGRX_BGRA')bandwidth: string- Bandwidth mode (default: 'highest')allowVideoFields: boolean- Allow video fields (default: true)name: string- Receiver name
Methods:
connect(source)- Connect to a sourcecapture(timeout?): CaptureResult- Capture any frame type (sync)captureAsync(timeout?): Promise<CaptureResult>- Capture any frame type asynchronously (non-blocking)captureVideo(timeout?): VideoFrame | null- Capture video only (sync)captureVideoAsync(timeout?): Promise<VideoFrame | null>- Capture video asynchronously (non-blocking)captureAudio(timeout?): AudioFrame | null- Capture audio only (sync)captureAudioAsync(timeout?): Promise<AudioFrame | null>- Capture audio asynchronously (non-blocking)setTally(tally): boolean- Set tally informationsendMetadata(frame)- Send metadata to sourcedestroy()- Release resources
PTZ Methods:
ptzIsSupported(): booleanptzZoom(zoom): booleanptzPanTilt(pan, tilt): booleanptzPanTiltSpeed(panSpeed, tiltSpeed): booleanptzStorePreset(presetNo): booleanptzRecallPreset(presetNo, speed?): booleanptzAutoFocus(): booleanptzFocus(focus): booleanptzWhiteBalanceAuto(): booleanptzExposureAuto(): boolean- And more...
Events:
'video'- Emitted when video frame is received'audio'- Emitted when audio frame is received'metadata'- Emitted when metadata is received'status_change'- Emitted when connection status changes'error'- Emitted on receive error
// Video pixel formats
ndi.FourCC.BGRA
ndi.FourCC.RGBA
ndi.FourCC.UYVY
ndi.FourCC.I420
ndi.FourCC.NV12
// ... and more
// Frame format types
ndi.FrameFormat.PROGRESSIVE
ndi.FrameFormat.INTERLEAVED
ndi.FrameFormat.FIELD_0
ndi.FrameFormat.FIELD_1
// Bandwidth modes
ndi.Bandwidth.HIGHEST
ndi.Bandwidth.LOWEST
ndi.Bandwidth.AUDIO_ONLY
ndi.Bandwidth.METADATA_ONLY
// Color formats
ndi.ColorFormat.BGRX_BGRA
ndi.ColorFormat.UYVY_BGRA
ndi.ColorFormat.RGBX_RGBA
ndi.ColorFormat.FASTEST
ndi.ColorFormat.BEST
// Frame types
ndi.FrameType.VIDEO
ndi.FrameType.AUDIO
ndi.FrameType.METADATA
ndi.FrameType.ERROR
ndi.FrameType.STATUS_CHANGESee the examples/ directory for complete examples using async/await:
finder.js- Discover NDI sources asynchronouslysender.js- Send video test pattern with async frame sendingreceiver.js- Receive video/audio with async capture loop
This project is licensed under the GNU General Public License v3.0 - see the LICENSE file for details.
NDI® is a registered trademark of NewTek, Inc.