grandi 1.3.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE +1 -1
- package/README.md +39 -446
- package/dist/index.d.mts +189 -217
- package/dist/index.mjs +252 -145
- package/dist/index.mjs.map +1 -1
- package/package.json +37 -27
package/NOTICE
CHANGED
package/README.md
CHANGED
|
@@ -1,475 +1,68 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<a href="https://tux-tn.github.io/grandi/">
|
|
3
|
+
<img src="./docs/public/mark.svg" alt="Grandi" width="96" height="96">
|
|
4
|
+
</a>
|
|
5
|
+
</p>
|
|
2
6
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Grandi is a TypeScript-first native Node.js binding for NewTek NDI™ 6. It exposes the full NDI 6 SDK surface area through a Promise-based API, providing strongly typed helpers for sending and receiving professional video, audio, metadata, and tally data from Node.js applications. For more information on NDI™ , see <https://www.ndi.tv/>.
|
|
6
|
-
|
|
7
|
-
## Why this fork?
|
|
8
|
-
Grandi is a maintained fork of [Streampunk Grandiose](https://github.com/Streampunk/grandiose). The original `steampunk/grandiose` repository and the corresponding `grandiose` package on npm have not been updated since 2023, and major functionality such as data sending never landed upstream. This fork modernizes the build, rewrites the bindings in TypeScript, and delivers the outstanding features required for production projects while keeping a familiar API. Meaningful contributions were also carried forward from community forks:
|
|
9
|
-
|
|
10
|
-
- TypeScript-focused rewrite with complete binding coverage.
|
|
11
|
-
- Improved NDI SDK handling (versioning, download/packaging, and portability improvements).
|
|
12
|
-
- Added utilities to make common workflows easier.
|
|
13
|
-
- Audio frame sending support from the [rse/grandiose](https://github.com/rse/grandiose) fork.
|
|
14
|
-
- Ad-hoc download of NDI SDK assets.
|
|
15
|
-
- Portability fixes for Windows, macOS, and Linux drawn from multiple forks.
|
|
16
|
-
- Sender, routing, and type-definition improvements from [ianshade/grandiose](https://github.com/ianshade/grandiose) and [hopejr/grandiose](https://github.com/hopejr/grandiose).
|
|
7
|
+
[](https://www.npmjs.com/package/grandi) [](https://github.com/tux-tn/grandi/actions/workflows/ci.yml) [](https://tux-tn.github.io/grandi/)
|
|
17
8
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
NDI™ was conceived as a grand vision for IP media transport. The earliest bindings leaned into that idea with the tongue‑in‑cheek name “gra‑NDI‑ose”. **Grandi** preserves the homage: it nods to “[grandiose](https://github.com/Streampunk/grandiose)”, itself drawn from the French word *grandi* (“grown”).
|
|
21
|
-
|
|
22
|
-
## Capabilities
|
|
23
|
-
- Native NDI™ 6 bindings written in modern TypeScript with out-of-the-box type definitions.
|
|
24
|
-
- Send and receive video, audio, metadata, and tally (program/preview) data streams.
|
|
25
|
-
- Promise-based API that keeps heavy lifting off the Node.js event loop.
|
|
26
|
-
- Helpers for working with choice of color formats, bandwidth limits, and audio layouts.
|
|
27
|
-
- Finder utilities for discovering sources on the local network or specified subnets.
|
|
28
|
-
- Built-in routing helpers for switching NDI sources without dropping frames.
|
|
29
|
-
|
|
30
|
-
### Missing & future work
|
|
31
|
-
- PTZ camera control APIs introduced in newer NDI SDK releases are not exposed yet.
|
|
32
|
-
- NDI receiver discovery (added in SDK v6.2) still needs bindings; today only sender discovery is available.
|
|
33
|
-
- Pull requests tackling these gaps (or other NDI 6.x+ features) are very welcome.
|
|
9
|
+
# Grandi
|
|
34
10
|
|
|
35
|
-
|
|
36
|
-
Grandi publishes per-architecture packages (`@grandi/<os>-<arch>`) that bundle the native addon and the matching NDI SDK runtime. Additional platforms can be compiled from source if the NDI SDK supports them.
|
|
11
|
+
Grandi provides TypeScript-first native Node.js bindings for the NDI™ 6 SDK. It supports discovery, receiving, sending, FrameSync, and routing.
|
|
37
12
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
13
|
+
- [Documentation](https://tux-tn.github.io/grandi/)
|
|
14
|
+
- [API reference](https://tux-tn.github.io/grandi/api/)
|
|
15
|
+
- [Examples](./examples)
|
|
16
|
+
- [Release notes](https://tux-tn.github.io/grandi/releases)
|
|
17
|
+
- [Changelog](./CHANGELOG.md)
|
|
43
18
|
|
|
44
19
|
## Installation
|
|
45
|
-
Install [Node.js](http://nodejs.org/) for your platform (tested against the current Long Term Support release). The main package automatically pulls the platform-specific optional dependency `@grandi/<os>-<arch>` when it exists, and falls back to building from source otherwise. Add the dependency to your project:
|
|
46
20
|
|
|
47
|
-
|
|
21
|
+
Grandi requires Node.js 20.19.5 or later. See the [installation guide](https://tux-tn.github.io/grandi/guide/installation) for platform requirements.
|
|
22
|
+
|
|
23
|
+
```sh
|
|
48
24
|
npm install grandi
|
|
49
|
-
# or
|
|
50
|
-
pnpm add grandi
|
|
51
|
-
# or
|
|
52
|
-
yarn add grandi
|
|
53
25
|
```
|
|
54
26
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
Grandi is designed to be `require`d or `import`ed from your own applications:
|
|
27
|
+
## Usage
|
|
58
28
|
|
|
59
29
|
```ts
|
|
60
30
|
import grandi from "grandi";
|
|
61
|
-
// or
|
|
62
|
-
const grandi = require("grandi");
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
## Build & distribution
|
|
66
|
-
Each platform package under the `@grandi/` scope bundles the native addon (`grandi.node`) and the corresponding NDI SDK runtime for that OS/arch. The root `grandi` package loads the matching optional dependency first and only compiles locally when no published package is available. Maintainers regenerate those packages from the official NDI SDK by running `node scripts/preinstall.mjs` (to download and stage the SDK) followed by a version bump (e.g., `node scripts/bump-version.mjs <version>`).
|
|
67
31
|
|
|
68
|
-
|
|
69
|
-
This module allows a Node.js program to find, receive, and send NDI™ video, audio, metadata, and tally streams over IP networks. All calls are asynchronous and use JavaScript promises with all of the underlying work of NDI running on separate threads from the event loop. The following sections recap the most common workflows; for complete runnable demos, see `examples/simple-receiver.mjs` and `examples/simple-sender.mjs`.
|
|
32
|
+
const finder = await grandi.find();
|
|
70
33
|
|
|
71
|
-
### Finding streams
|
|
72
|
-
`grandi.find` resolves to a finder handle that can block for network updates and expose the discovered sources:
|
|
73
|
-
|
|
74
|
-
```ts
|
|
75
|
-
import { setTimeout as sleep } from "node:timers/promises";
|
|
76
|
-
import grandi from "grandi";
|
|
77
|
-
|
|
78
|
-
async function pickSource() {
|
|
79
|
-
const finder = await grandi.find({ showLocalSources: true });
|
|
80
|
-
try {
|
|
81
|
-
for (let attempts = 0; attempts < 20; attempts++) {
|
|
82
|
-
finder.wait(250); // block for up to 250 ms of new announcements
|
|
83
|
-
const sources = finder.sources();
|
|
84
|
-
if (sources.length > 0) return sources[0];
|
|
85
|
-
await sleep(250);
|
|
86
|
-
}
|
|
87
|
-
throw new Error("No NDI sources found on the network.");
|
|
88
|
-
} finally {
|
|
89
|
-
finder.destroy();
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
`find` accepts a single options object. Common filters:
|
|
95
|
-
|
|
96
|
-
```ts
|
|
97
|
-
await grandi.find({
|
|
98
|
-
showLocalSources: true, // include sources on the same machine
|
|
99
|
-
groups: "studio3", // comma-separated list of group names
|
|
100
|
-
extraIPs: "192.168.1.122,mixer.local", // comma-separated IPs/hosts outside mDNS
|
|
101
|
-
});
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
### Receiving streams
|
|
105
|
-
First, find a stream or construct a `Source` object:
|
|
106
|
-
|
|
107
|
-
```ts
|
|
108
|
-
const grandi = require("grandi");
|
|
109
|
-
const source = { name: "<source_name>", urlAddress: "<ip>:<port>" };
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
Create a receiver:
|
|
113
|
-
|
|
114
|
-
```ts
|
|
115
|
-
const receiver = await grandi.receive({ source });
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
Example receiver object:
|
|
119
|
-
|
|
120
|
-
```ts
|
|
121
|
-
{
|
|
122
|
-
embedded: [External],
|
|
123
|
-
video: [Function],
|
|
124
|
-
audio: [Function],
|
|
125
|
-
metadata: [Function],
|
|
126
|
-
data: [Function],
|
|
127
|
-
source: { name: "LEMARR (Test Pattern)", urlAddress: "169.254.82.1:5961" },
|
|
128
|
-
colorFormat: 100, // grandi.COLOR_FORMAT_FASTEST
|
|
129
|
-
bandwidth: 100, // grandi.BANDWIDTH_HIGHEST
|
|
130
|
-
allowVideoFields: true
|
|
131
|
-
}
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
Configure receivers via options:
|
|
135
|
-
|
|
136
|
-
```ts
|
|
137
|
-
const receiver = await grandi.receive({
|
|
138
|
-
source,
|
|
139
|
-
colorFormat: grandi.COLOR_FORMAT_UYVY_RGBA,
|
|
140
|
-
bandwidth: grandi.BANDWIDTH_AUDIO_ONLY,
|
|
141
|
-
allowVideoFields: true,
|
|
142
|
-
name: "rooftop"
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
#### Video
|
|
148
|
-
|
|
149
|
-
```ts
|
|
150
|
-
const timeout = 5000;
|
|
151
34
|
try {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
} catch (err) {
|
|
157
|
-
console.error(err);
|
|
35
|
+
await finder.wait(1_000);
|
|
36
|
+
console.log(finder.sources());
|
|
37
|
+
} finally {
|
|
38
|
+
finder.destroy();
|
|
158
39
|
}
|
|
159
40
|
```
|
|
160
41
|
|
|
161
|
-
|
|
42
|
+
See the documentation for these workflows:
|
|
162
43
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
frameRateD: 1001,
|
|
170
|
-
pictureAspectRatio: 1.7777777910232544,
|
|
171
|
-
timestamp: [1538569443, 717845600],
|
|
172
|
-
frameFormatType: grandi.FORMAT_TYPE_INTERLACED,
|
|
173
|
-
timecode: [0, 0],
|
|
174
|
-
lineStrideBytes: 3840,
|
|
175
|
-
data: <Buffer 80 10 80 10 ... >
|
|
176
|
-
}
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
#### Audio
|
|
180
|
-
|
|
181
|
-
```ts
|
|
182
|
-
const audioFrame = await receiver.audio(
|
|
183
|
-
{
|
|
184
|
-
audioFormat: grandi.AUDIO_FORMAT_INT_16_INTERLEAVED,
|
|
185
|
-
referenceLevel: 0
|
|
186
|
-
},
|
|
187
|
-
8000
|
|
188
|
-
);
|
|
189
|
-
```
|
|
44
|
+
- [Discover sources](https://tux-tn.github.io/grandi/guide/discovery)
|
|
45
|
+
- [Receive media](https://tux-tn.github.io/grandi/guide/receiving)
|
|
46
|
+
- [Send media](https://tux-tn.github.io/grandi/guide/sending)
|
|
47
|
+
- [Frame synchronization](https://tux-tn.github.io/grandi/guide/frame-sync)
|
|
48
|
+
- [Route sources](https://tux-tn.github.io/grandi/guide/routing)
|
|
49
|
+
- [Electron and bundlers](https://tux-tn.github.io/grandi/guide/electron-bundlers)
|
|
190
50
|
|
|
191
|
-
|
|
51
|
+
For a complete Electron application, see the [Electron NDI viewer](https://github.com/tux-tn/electron-ndi-viewer).
|
|
192
52
|
|
|
193
|
-
|
|
194
|
-
{
|
|
195
|
-
type: "audio",
|
|
196
|
-
audioFormat: grandi.AUDIO_FORMAT_INT_16_INTERLEAVED,
|
|
197
|
-
referenceLevel: 0,
|
|
198
|
-
sampleRate: 48000,
|
|
199
|
-
channels: 4,
|
|
200
|
-
samples: 4800,
|
|
201
|
-
channelStrideInBytes: 9600,
|
|
202
|
-
timestamp: [1538578787, 132614500],
|
|
203
|
-
timecode: [0, 800000000],
|
|
204
|
-
data: <Buffer 00 00 00 00 ... >
|
|
205
|
-
}
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
#### Metadata
|
|
53
|
+
## Development
|
|
209
54
|
|
|
210
|
-
```
|
|
211
|
-
|
|
55
|
+
```sh
|
|
56
|
+
npm run build
|
|
57
|
+
npm test
|
|
58
|
+
npm run lint
|
|
59
|
+
npm run format
|
|
212
60
|
```
|
|
213
61
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
#### Next available data
|
|
217
|
-
|
|
218
|
-
```ts
|
|
219
|
-
const prefs = { audioFormat: grandi.AUDIO_FORMAT_FLOAT_32_SEPARATE };
|
|
220
|
-
const payload = await receiver.data(prefs, 1000); // 1s timeout
|
|
221
|
-
if (payload.type === "video") {
|
|
222
|
-
// handle video frame
|
|
223
|
-
} else if (payload.type === "metadata") {
|
|
224
|
-
console.log(payload.data);
|
|
225
|
-
}
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
### Frame synchronization (pull-based playback)
|
|
229
|
-
If you want smoother playback clocked to your own loop (e.g., GPU vsync) instead of reacting to pushed frames, wrap an existing receiver in an NDI frame-synchronizer:
|
|
230
|
-
|
|
231
|
-
```ts
|
|
232
|
-
const receiver = await grandi.receive({ source, colorFormat: grandi.ColorFormat.Fastest });
|
|
233
|
-
const fs = await grandi.framesync(receiver);
|
|
234
|
-
|
|
235
|
-
// Pull video; returns { type: "timeout" } until first frame arrives.
|
|
236
|
-
const video = await fs.video(grandi.FrameType.Progressive);
|
|
237
|
-
|
|
238
|
-
// Pull audio resampled to your requested cadence.
|
|
239
|
-
const audio = await fs.audio({ sampleRate: 48_000, noChannels: 2, noSamples: 1600 });
|
|
240
|
-
|
|
241
|
-
fs.destroy(); // destroy frame-sync first
|
|
242
|
-
receiver.destroy(); // then destroy the receiver
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
Notes:
|
|
246
|
-
- `fs.video()` and `fs.audio()` always return immediately; they may duplicate/drop frames to match your call rate.
|
|
247
|
-
- Always destroy the frame-sync before destroying the receiver it wraps.
|
|
248
|
-
|
|
249
|
-
### Sending streams
|
|
250
|
-
Sending is fully supported. Call `grandi.send` with a `SendOptions` object to create a `Sender` instance capable of video, audio, metadata, and tally interactions:
|
|
251
|
-
|
|
252
|
-
```ts
|
|
253
|
-
const sender = await grandi.send({
|
|
254
|
-
name: "grandi-demo",
|
|
255
|
-
groups: "studio3",
|
|
256
|
-
clockVideo: true,
|
|
257
|
-
clockAudio: true
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
const timecode = process.hrtime.bigint() / 100n;
|
|
261
|
-
const timestampNs = process.hrtime.bigint();
|
|
262
|
-
const timestamp = [
|
|
263
|
-
Number(timestampNs / 1_000_000_000n),
|
|
264
|
-
Number(timestampNs % 1_000_000_000n),
|
|
265
|
-
];
|
|
266
|
-
|
|
267
|
-
// Prepare frame payloads (see examples/simple-sender.mjs for helpers)
|
|
268
|
-
|
|
269
|
-
await sender.video({
|
|
270
|
-
xres: 1280,
|
|
271
|
-
yres: 720,
|
|
272
|
-
frameRateN: 30,
|
|
273
|
-
frameRateD: 1,
|
|
274
|
-
pictureAspectRatio: 16 / 9,
|
|
275
|
-
frameFormatType: grandi.FORMAT_TYPE_PROGRESSIVE,
|
|
276
|
-
lineStrideBytes: 1280 * 4,
|
|
277
|
-
data: whiteBuffer, // BGRA pixels
|
|
278
|
-
fourCC: grandi.FOURCC_BGRA,
|
|
279
|
-
timecode,
|
|
280
|
-
timestamp,
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
await sender.audio({
|
|
284
|
-
sampleRate: 48_000,
|
|
285
|
-
noChannels: 2,
|
|
286
|
-
noSamples: 1600,
|
|
287
|
-
channelStrideBytes: 1600 * 4,
|
|
288
|
-
data: silentAudioBuffer,
|
|
289
|
-
fourCC: grandi.FOURCC_FLTp,
|
|
290
|
-
timecode,
|
|
291
|
-
timestamp,
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
# Example metadata, that explicitly enable hardware acceleration in receiver
|
|
295
|
-
sender.metadata("<ndi_video_codec type=\"hardware\"/>");
|
|
296
|
-
|
|
297
|
-
const tally = sender.tally();
|
|
298
|
-
console.log(tally.on_program, tally.on_preview);
|
|
299
|
-
```
|
|
300
|
-
|
|
301
|
-
Destroy senders and receivers explicitly when finished to release NDI resources.
|
|
302
|
-
|
|
303
|
-
### Other helpers
|
|
304
|
-
- `grandi.version()` returns the NDI SDK version string (e.g. `NDI SDK LINUX 10:24:11 Aug 21 2025 6.2.1.0`).
|
|
305
|
-
- `grandi.isSupportedCPU()` checks whether the host CPU can run the NDI SDK.
|
|
306
|
-
- `grandi.initialize()` / `grandi.destroy()` control the lifetime of the underlying library in advanced scenarios.
|
|
307
|
-
|
|
308
|
-
## Benchmark
|
|
309
|
-
A loopback benchmark script measures end-to-end send/receive throughput and latency on your machine. It spins up a sender and receiver locally, so a working NDI setup with discovery is required.
|
|
310
|
-
|
|
311
|
-
- Build first if needed (`npm run build`), then run `npm run bench -- [options]`. The script defaults to 1080p30 with audio and colored output.
|
|
312
|
-
|
|
313
|
-
| Option | Default | Description |
|
|
314
|
-
| --- | --- | --- |
|
|
315
|
-
| `--mode realtime|throughput` | `realtime` | `realtime` keeps to the target FPS with clocking enabled; `throughput` sends as fast as possible. |
|
|
316
|
-
| `--duration <sec>` | `5` | Run length in seconds. |
|
|
317
|
-
| `--fps <num>` | `30` | Target frames per second. |
|
|
318
|
-
| `--width <px>` | `1920` | Video width in pixels. |
|
|
319
|
-
| `--height <px>` | `1080` | Video height in pixels. |
|
|
320
|
-
| `--no-audio` | audio on | Disable sending/receiving audio. |
|
|
321
|
-
| `--framesync` | off | Pull frames via the NDI frame-sync API for smoother capture. |
|
|
322
|
-
| `--gc-every <ms>` | `500` | Force `global.gc()` at this interval when `--expose-gc` is enabled. |
|
|
323
|
-
| `--no-color` | color on | Disable ANSI color output. |
|
|
324
|
-
|
|
325
|
-
`npm run bench` adds `--expose-gc` to keep buffer reuse stable during long runs.
|
|
326
|
-
|
|
327
|
-
Example (10s, 1080p30, realtime):
|
|
328
|
-
|
|
329
|
-
```bash
|
|
330
|
-
npm run bench -- --duration 10 --mode realtime --fps 30 --framesync
|
|
331
|
-
```
|
|
332
|
-
|
|
333
|
-
Example (throughput stress, 720p, video only):
|
|
334
|
-
|
|
335
|
-
```bash
|
|
336
|
-
npm run bench -- --mode throughput --width 1280 --height 720 --no-audio --no-color
|
|
337
|
-
```
|
|
338
|
-
|
|
339
|
-
## Contributing
|
|
340
|
-
Ready to hack on Grandi? Here’s the typical workflow.
|
|
341
|
-
|
|
342
|
-
1. **Install prerequisites**
|
|
343
|
-
- Node.js ≥ 20.19.5 and a working C/C++ toolchain for your platform.
|
|
344
|
-
- Git LFS is *not* required; the NDI SDK is fetched dynamically.
|
|
345
|
-
2. **Download the NDI SDK (maintainers)**
|
|
346
|
-
- Run `node scripts/build-addon.mjs` to download and unpack the official NDI SDK into `ndi/` and populate the per-arch package folders under `packages/` with the right runtime files.
|
|
347
|
-
- Consumers don’t need this step; they install the appropriate `@grandi/<os>-<arch>` package from npm automatically.
|
|
348
|
-
3. **TypeScript build**
|
|
349
|
-
- `npm run build` compiles `src/` via `tsdown`, emitting ESM/CJS bundles and declaration files in `dist/`.
|
|
350
|
-
4. **Native addon rebuild**
|
|
351
|
-
- If you change C/C++ files under `lib/`, recompile with `npx node-gyp-build` (or simply re-run `npm install`). This uses the same loader that consumers invoke at runtime.
|
|
352
|
-
5. **Testing**
|
|
353
|
-
- `npm test` runs the full Vitest suite (unit + integration stubs).
|
|
354
|
-
- `npm run test:unit` focuses on pure JS/TS tests.
|
|
355
|
-
- `npm run test:integration` exercises the native bindings against a real NDI environment;
|
|
356
|
-
- `npm run test:coverage` provides coverage data via `@vitest/coverage-v8`.
|
|
357
|
-
6. **Linting & formatting**
|
|
358
|
-
- `npm run lint` / `npm run format` cover the TypeScript/JavaScript sources via Biome.
|
|
359
|
-
- `npm run format:cpp` formats the native sources with `clang-format`.
|
|
360
|
-
7. **Manual verification**
|
|
361
|
-
- `node examples/simple-sender.mjs` and `node examples/simple-receiver.mjs` are quick smoke tests for the send/receive API.
|
|
362
|
-
|
|
363
|
-
Before opening a pull request, make sure the linter, formatter, and tests all pass, and include context for any platform-specific considerations (e.g., SDK versions, OS dependencies).
|
|
364
|
-
|
|
365
|
-
## API reference
|
|
366
|
-
This section documents every exported method and type surfaced by the module. Refer to `src/index.ts` and `src/types.ts` for authoritative definitions.
|
|
367
|
-
|
|
368
|
-
### Module exports
|
|
369
|
-
|
|
370
|
-
| Export | Returns | Purpose |
|
|
371
|
-
| --- | --- | --- |
|
|
372
|
-
| `find(options?: FindOptions)` | `Promise<Finder>` | Discover available NDI sources, optionally filtering by groups, local visibility, or explicit IPs. Resolves to a finder whose `sources()` method exposes the latest snapshot. |
|
|
373
|
-
| `receive(options: ReceiveOptions)` | `Promise<Receiver>` | Create a receiver bound to a specific `Source` with optional color format, bandwidth, interlaced, and naming tweaks. |
|
|
374
|
-
| `send(options: SendOptions)` | `Promise<Sender>` | Create a sender that can push video, audio, metadata, and tally updates into the NDI network. |
|
|
375
|
-
| `routing(params: { name?: string; groups?: string })` | `Promise<Routing>` | Build an NDI router that can switch downstream destinations to new sources via `routing.change`. |
|
|
376
|
-
| `initialize()` / `destroy()` | `boolean` | Manually initialize or tear down the shared NDI state; normally handled automatically by `send`/`receive`. |
|
|
377
|
-
| `version()` | `string` | Report the bundled NDI SDK version. |
|
|
378
|
-
| `isSupportedCPU()` | `boolean` | Guard call to confirm the host CPU meets the NDI SDK requirements. |
|
|
379
|
-
| `ColorFormat`, `AudioFormat`, `Bandwidth`, `FrameType`, `FourCC` | enums | Enumerations re-exported for convenience (also mirrored as constants on the default export). |
|
|
380
|
-
| `default` | `typeof grandi` | The `grandi` object containing the methods above plus constant aliases such as `COLOR_FORMAT_FASTEST`, `BANDWIDTH_HIGHEST`, etc. |
|
|
381
|
-
|
|
382
|
-
### Receiver interface
|
|
383
|
-
`Receiver` instances expose:
|
|
384
|
-
|
|
385
|
-
| Call | Returns | Purpose |
|
|
386
|
-
| --- | --- | --- |
|
|
387
|
-
| `video(timeoutMs?: number)` | `Promise<ReceivedVideoFrame>` | Resolve with the next available video frame. |
|
|
388
|
-
| `audio(optionsOrTimeout?: AudioReceiveOptions \| number, timeoutMs?: number)` | `Promise<ReceivedAudioFrame>` | Fetch audio, overriding format/reference level per call if desired. |
|
|
389
|
-
| `metadata(timeoutMs?: number)` | `Promise<ReceivedMetadataFrame>` | Receive metadata frames (XML strings). |
|
|
390
|
-
| `data(optionsOrTimeout?: AudioReceiveOptions \| number, timeoutMs?: number)` | `Promise<ReceiverDataFrame>` | Return whichever payload (video/audio/metadata/source change/status change) arrives first. |
|
|
391
|
-
| `tally(state: ReceiverTallyState)` | `boolean` | Push tally states (program/preview) upstream to the sender. |
|
|
392
|
-
| `destroy()` | `boolean` | Release the underlying NDI receiver resources. |
|
|
393
|
-
|
|
394
|
-
Properties: `embedded`, `source`, `colorFormat`, `bandwidth`, `allowVideoFields`, `name`.
|
|
395
|
-
|
|
396
|
-
### Sender interface
|
|
397
|
-
`Sender` instances surface:
|
|
398
|
-
|
|
399
|
-
| Call | Returns | Purpose |
|
|
400
|
-
| --- | --- | --- |
|
|
401
|
-
| `video(frame: VideoFrame)` | `Promise<void>` | Send a video frame into the NDI network. |
|
|
402
|
-
| `audio(frame: AudioFrame)` | `Promise<void>` | Send an audio frame. |
|
|
403
|
-
| `metadata(data: string)` | `boolean` | Push metadata strings (XML). |
|
|
404
|
-
| `tally()` | `SenderTally` | Retrieve current tally state (program/preview booleans plus `changed`). |
|
|
405
|
-
| `connections()` | `number` | Report how many receivers are actively connected. |
|
|
406
|
-
| `sourcename()` | `string` | Fully qualified source name visible on the network. |
|
|
407
|
-
| `destroy()` | `boolean` | Dispose the sender and release network resources. |
|
|
408
|
-
|
|
409
|
-
Properties: `embedded`, `name`, `groups`, `clockVideo`, `clockAudio`.
|
|
410
|
-
|
|
411
|
-
### Routing interface
|
|
412
|
-
|
|
413
|
-
| Call | Returns | Purpose |
|
|
414
|
-
| --- | --- | --- |
|
|
415
|
-
| `change(source: Source)` | `boolean` | Switch downstream destinations to a new source. |
|
|
416
|
-
| `clear()` | `boolean` | Disconnect the current source. |
|
|
417
|
-
| `connections()` | `number` | Monitor downstream subscriptions. |
|
|
418
|
-
| `sourcename()` | `string` | Current routed source name. |
|
|
419
|
-
| `destroy()` | `boolean` | Cleanly shut down the router. |
|
|
420
|
-
|
|
421
|
-
### Finder interface
|
|
422
|
-
|
|
423
|
-
| Call | Returns | Purpose |
|
|
424
|
-
| --- | --- | --- |
|
|
425
|
-
| `sources()` | `Source[]` | Latest snapshot of discovered sources. |
|
|
426
|
-
| `wait(timeoutMs?: number)` | `boolean` | Block for network updates up to the given timeout. |
|
|
427
|
-
| `destroy()` | `boolean` | Dispose the finder and background threads. |
|
|
428
|
-
|
|
429
|
-
### Options and helper types
|
|
430
|
-
|
|
431
|
-
| Type | Shape | Notes |
|
|
432
|
-
| --- | --- | --- |
|
|
433
|
-
| `Source` | `{ name: string; urlAddress?: string }` | Identifies an NDI endpoint. |
|
|
434
|
-
| `FindOptions` | `{ showLocalSources?: boolean; groups?: string; extraIPs?: string; }` | Filters applied when discovering sources. |
|
|
435
|
-
| `ReceiveOptions` | `{ source: Source; colorFormat?: ColorFormat; bandwidth?: Bandwidth; allowVideoFields?: boolean; name?: string; }` | Required to create receivers with optional overrides. |
|
|
436
|
-
| `SendOptions` | `{ name: string; groups?: string; clockVideo?: boolean; clockAudio?: boolean; }` | Configure sender identity and sync behavior. |
|
|
437
|
-
| `AudioReceiveOptions` | `{ audioFormat?: AudioFormat; referenceLevel?: number; }` | Used when calling `receiver.audio` or `receiver.data`. |
|
|
438
|
-
| `ReceiverTallyState` | `{ onProgram?: boolean; onPreview?: boolean; }` | Provided to `receiver.tally` to reflect monitoring state. |
|
|
439
|
-
| `SenderTally` | `{ changed: boolean; on_program: boolean; on_preview: boolean; }` | Returned by `sender.tally`. |
|
|
440
|
-
| `PtpTimestamp` | `[seconds, nanoseconds]` | Tuple aligning with the NDI SDK timestamp APIs. |
|
|
441
|
-
| `Timecode` | `bigint \| number \| PtpTimestamp` | Matches how timecodes are expressed throughout the SDK. |
|
|
442
|
-
|
|
443
|
-
### Frame payload types
|
|
444
|
-
|
|
445
|
-
| Type | Description |
|
|
446
|
-
| --- | --- |
|
|
447
|
-
| `VideoFrame` | Outbound frame with resolution, frame rate (`frameRateN`/`frameRateD`), aspect ratio, `fourCC`, `frameFormatType`, `lineStrideBytes`, `data`, optional `timecode`, `timestamp`, `metadata`. |
|
|
448
|
-
| `ReceivedVideoFrame` | `VideoFrame` plus `type: "video"` and non-optional `timecode`/`timestamp`. |
|
|
449
|
-
| `AudioFrame` | Outbound audio payload including sample rate, channels/samples, stride, `data`, `fourCC`, optional timing/metadata. |
|
|
450
|
-
| `ReceivedAudioFrame` | Inbound audio payload with `audioFormat`, `referenceLevel`, `timecode`, `timestamp`. |
|
|
451
|
-
| `ReceivedMetadataFrame` | Metadata payload (`type: "metadata"`, `length`, `timecode`, `timestamp`, `data` string). |
|
|
452
|
-
| `ReceiverDataFrame` | Discriminated union of `ReceivedVideoFrame \| ReceivedAudioFrame \| ReceivedMetadataFrame \| SourceChangeEvent \| StatusChangeEvent`. |
|
|
453
|
-
| `SourceChangeEvent` / `StatusChangeEvent` | Internal notifications delivered via `receiver.data` when the remote sender changes or becomes unavailable. |
|
|
454
|
-
|
|
455
|
-
### Enum reference
|
|
456
|
-
- `ColorFormat` — enumerates `BGRX_BGRA`, `UYVY_BGRA`, `RGBX_RGBA`, `UYVY_RGBA`, `Fastest`, `Best`, and `BGRX_BGRA_FLIPPED`. The `grandi.COLOR_FORMAT_*` constants map to these values.
|
|
457
|
-
- `AudioFormat` — `Float32Separate`, `Float32Interleaved`, `Int16Interleaved`. Access via `grandi.AUDIO_FORMAT_*`.
|
|
458
|
-
- `Bandwidth` — `MetadataOnly`, `AudioOnly`, `Lowest`, `Highest`. Access via `grandi.BANDWIDTH_*`.
|
|
459
|
-
- `FrameType` — `Interlaced`, `Progressive`, `Field0`, `Field1`. Access via `grandi.FORMAT_TYPE_*`.
|
|
460
|
-
- `FourCC` — common pixel/audio format codes (`UYVY`, `UYVA`, `P216`, `PA16`, `YV12`, `I420`, `NV12`, `BGRA`, `BGRX`, `RGBA`, `RGBX`, `FLTp`). Helper constants exist on the default export.
|
|
461
|
-
|
|
462
|
-
## Status, support, and further development
|
|
463
|
-
Support for sending and receiving streams across Windows, macOS, and Linux platforms is actively maintained. Contributions are welcome via pull requests, and enhancements or bug reports can be filed as GitHub issues.
|
|
62
|
+
Run the local NDI benchmark with `npm run bench`. Native source builds use `npm run build:addon`.
|
|
464
63
|
|
|
465
64
|
## License
|
|
466
|
-
Apart from the exceptions below, this software is released under the Apache 2.0 license. See [LICENSE](./LICENSE) for details. Attribution: this project remains a maintained fork of Streampunk Grandiose, and portions derive from that project and community forks. See [NOTICE](./NOTICE) for attributions.
|
|
467
|
-
|
|
468
|
-
### License exceptions
|
|
469
|
-
The software uses libraries provided under a royalty-free license from NewTek, Inc. (see the [NDI SDK License](https://ndi.link/ndisdk_license) for full terms):
|
|
470
65
|
|
|
471
|
-
|
|
472
|
-
- The DLL and library files are provided for installation convenience and are covered by the NewTek license in the scoped packages under `packages/` (published as `@grandi/*`).
|
|
66
|
+
Grandi uses the Apache 2.0 license. See [LICENSE](./LICENSE) and [NOTICE](./NOTICE) for license and attribution details.
|
|
473
67
|
|
|
474
|
-
|
|
475
|
-
NDI™ is a trademark of NewTek, Inc.
|
|
68
|
+
The NDI SDK files use the [NDI SDK License](https://ndi.link/ndisdk_license). NDI™ is a trademark of NewTek, Inc.
|