@stagetimerio/grandiose 0.1.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.
@@ -0,0 +1,792 @@
1
+ /* Copyright 2018 Streampunk Media Ltd.
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
14
+ */
15
+
16
+ #include <string>
17
+ #include <Processing.NDI.Lib.h>
18
+
19
+ #ifdef _WIN32
20
+ #ifdef _WIN64
21
+ #pragma comment(lib, "Processing.NDI.Lib.x64.lib")
22
+ #else // _WIN64
23
+ #pragma comment(lib, "Processing.NDI.Lib.x86.lib")
24
+ #endif // _WIN64
25
+ #endif // _WIN32
26
+
27
+ #include "grandiose_send.h"
28
+ #include "grandiose_util.h"
29
+
30
+ napi_value videoSend(napi_env env, napi_callback_info info);
31
+ napi_value audioSend(napi_env env, napi_callback_info info);
32
+ napi_value connections(napi_env env, napi_callback_info info);
33
+ napi_value tally(napi_env env, napi_callback_info info);
34
+ napi_value sourcename(napi_env env, napi_callback_info info);
35
+
36
+ void sendExecute(napi_env env, void* data) {
37
+ sendCarrier* c = (sendCarrier *) data;
38
+
39
+ NDIlib_send_create_t NDI_send_create_desc;
40
+
41
+ NDI_send_create_desc.p_ndi_name = c->name;
42
+ NDI_send_create_desc.p_groups = c->groups;
43
+ NDI_send_create_desc.clock_video = c->clockVideo;
44
+ NDI_send_create_desc.clock_audio = c->clockAudio;
45
+ c->send = NDIlib_send_create(&NDI_send_create_desc);
46
+ if (!c->send) {
47
+ c->status = GRANDIOSE_SEND_CREATE_FAIL;
48
+ c->errorMsg = "Failed to create NDI sender.";
49
+ return;
50
+ }
51
+ }
52
+
53
+ /* implicit destruction of NDI sender via garbage collection */
54
+ void finalizeSend(napi_env env, void* data, void* hint) {
55
+ /* fetch NDI sender wrapper object */
56
+ napi_value obj = (napi_value)hint;
57
+
58
+ /* fetch NDI sender external object */
59
+ napi_value sendValue;
60
+ if (napi_get_named_property(env, obj, "embedded", &sendValue) != napi_ok)
61
+ return;
62
+
63
+ /* ensure it was still not manually destroyed */
64
+ napi_valuetype result;
65
+ if (napi_typeof(env, sendValue, &result) != napi_ok)
66
+ return;
67
+ if (result != napi_external)
68
+ return;
69
+
70
+ /* fetch NDI sender native object */
71
+ void *sendData;
72
+ if (napi_get_value_external(env, sendValue, &sendData) != napi_ok)
73
+ return;
74
+ NDIlib_send_instance_t send = (NDIlib_send_instance_t)sendData;
75
+
76
+ /* call the NDI API */
77
+ NDIlib_send_destroy(send);
78
+ }
79
+
80
+ /* explicit destruction of NDI sender via "destroy" method */
81
+ napi_value destroySend(napi_env env, napi_callback_info info) {
82
+ /* create a new Promise carrier object */
83
+ carrier *c = new carrier;
84
+ napi_value promise;
85
+ c->status = napi_create_promise(env, &c->_deferred, &promise);
86
+ REJECT_RETURN;
87
+
88
+ /* fetch the NDI sender wrapper object ("this" of the "destroy" method) */
89
+ size_t argc = 1;
90
+ napi_value args[1];
91
+ napi_value thisValue;
92
+ c->status = napi_get_cb_info(env, info, &argc, args, &thisValue, nullptr);
93
+ REJECT_RETURN;
94
+
95
+ /* fetch NDI sender external object */
96
+ napi_value sendValue;
97
+ c->status = napi_get_named_property(env, thisValue, "embedded", &sendValue);
98
+ REJECT_RETURN;
99
+
100
+ /* ensure it was still not manually destroyed */
101
+ napi_valuetype result;
102
+ if (napi_typeof(env, sendValue, &result) != napi_ok)
103
+ NAPI_THROW_ERROR("NDI sender already destroyed");
104
+ if (result == napi_external) {
105
+ /* fetch NDI sender native object */
106
+ void *sendData;
107
+ c->status = napi_get_value_external(env, sendValue, &sendData);
108
+ REJECT_RETURN;
109
+ NDIlib_send_instance_t send = (NDIlib_send_instance_t)sendData;
110
+
111
+ /* call the NDI API */
112
+ NDIlib_send_destroy(send);
113
+
114
+ /* overwrite the "embedded" field with a non-external value
115
+ (to ensure that the "finalizeSend" will no longer do anything
116
+ once the garbage collection fires) */
117
+ napi_value value;
118
+ napi_create_int32(env, 0, &value);
119
+ c->status = napi_set_named_property(env, thisValue, "embedded", value);
120
+ REJECT_RETURN;
121
+ }
122
+
123
+ napi_value undefined;
124
+ napi_get_undefined(env, &undefined);
125
+ napi_resolve_deferred(env, c->_deferred, undefined);
126
+
127
+ return promise;
128
+ }
129
+
130
+ void sendComplete(napi_env env, napi_status asyncStatus, void* data) {
131
+ sendCarrier* c = (sendCarrier*) data;
132
+
133
+ if (asyncStatus != napi_ok) {
134
+ c->status = asyncStatus;
135
+ c->errorMsg = "Async sender creation failed to complete.";
136
+ }
137
+ REJECT_STATUS;
138
+
139
+ napi_value result;
140
+ c->status = napi_create_object(env, &result);
141
+ REJECT_STATUS;
142
+
143
+ napi_value embedded;
144
+ c->status = napi_create_external(env, c->send, finalizeSend, result, &embedded);
145
+ REJECT_STATUS;
146
+ c->status = napi_set_named_property(env, result, "embedded", embedded);
147
+ REJECT_STATUS;
148
+
149
+ napi_value destroyFn;
150
+ c->status = napi_create_function(env, "destroy", NAPI_AUTO_LENGTH, destroySend,
151
+ nullptr, &destroyFn);
152
+ REJECT_STATUS;
153
+ c->status = napi_set_named_property(env, result, "destroy", destroyFn);
154
+ REJECT_STATUS;
155
+
156
+ napi_value videoFn;
157
+ c->status = napi_create_function(env, "video", NAPI_AUTO_LENGTH, videoSend,
158
+ nullptr, &videoFn);
159
+ REJECT_STATUS;
160
+ c->status = napi_set_named_property(env, result, "video", videoFn);
161
+ REJECT_STATUS;
162
+
163
+ napi_value audioFn;
164
+ c->status = napi_create_function(env, "audio", NAPI_AUTO_LENGTH, audioSend,
165
+ nullptr, &audioFn);
166
+ REJECT_STATUS;
167
+ c->status = napi_set_named_property(env, result, "audio", audioFn);
168
+ REJECT_STATUS;
169
+
170
+ napi_value connectionsFn;
171
+ c->status = napi_create_function(env, "connections", NAPI_AUTO_LENGTH, connections,
172
+ nullptr, &connectionsFn);
173
+ REJECT_STATUS;
174
+ c->status = napi_set_named_property(env, result, "connections", connectionsFn);
175
+ REJECT_STATUS;
176
+
177
+ napi_value tallyFn;
178
+ c->status = napi_create_function(env, "connections", NAPI_AUTO_LENGTH, tally,
179
+ nullptr, &tallyFn);
180
+ REJECT_STATUS;
181
+ c->status = napi_set_named_property(env, result, "tally", tallyFn);
182
+ REJECT_STATUS;
183
+
184
+ napi_value sourcenameFn;
185
+ c->status = napi_create_function(env, "sourcename", NAPI_AUTO_LENGTH, sourcename,
186
+ nullptr, &sourcenameFn);
187
+ REJECT_STATUS;
188
+ c->status = napi_set_named_property(env, result, "sourcename", sourcenameFn);
189
+ REJECT_STATUS;
190
+
191
+ // napi_value metadataFn;
192
+ // c->status = napi_create_function(env, "metadata", NAPI_AUTO_LENGTH, metadataReceive,
193
+ // nullptr, &metadataFn);
194
+ // REJECT_STATUS;
195
+ // c->status = napi_set_named_property(env, result, "metadata", metadataFn);
196
+ // REJECT_STATUS;
197
+
198
+ // napi_value dataFn;
199
+ // c->status = napi_create_function(env, "data", NAPI_AUTO_LENGTH, dataReceive,
200
+ // nullptr, &dataFn);
201
+ // REJECT_STATUS;
202
+ // c->status = napi_set_named_property(env, result, "data", dataFn);
203
+ // REJECT_STATUS;
204
+
205
+ // napi_value name, groups, clockVideo, clockAudio;
206
+ napi_value name, clockVideo, clockAudio;
207
+ c->status = napi_create_string_utf8(env, c->name, NAPI_AUTO_LENGTH, &name);
208
+ REJECT_STATUS;
209
+ c->status = napi_set_named_property(env, result, "name", name);
210
+ REJECT_STATUS;
211
+
212
+ c->status = napi_get_boolean(env, c->clockVideo, &clockVideo);
213
+ REJECT_STATUS;
214
+ c->status = napi_set_named_property(env, result, "clockVideo", clockVideo);
215
+ REJECT_STATUS;
216
+
217
+ c->status = napi_get_boolean(env, c->clockAudio, &clockAudio);
218
+ REJECT_STATUS;
219
+ c->status = napi_set_named_property(env, result, "clockAudio", clockAudio);
220
+ REJECT_STATUS;
221
+
222
+ napi_status status;
223
+ status = napi_resolve_deferred(env, c->_deferred, result);
224
+ FLOATING_STATUS;
225
+
226
+ tidyCarrier(env, c);
227
+ }
228
+
229
+ napi_value send(napi_env env, napi_callback_info info) {
230
+ napi_valuetype type;
231
+ sendCarrier* c = new sendCarrier;
232
+
233
+ napi_value promise;
234
+ c->status = napi_create_promise(env, &c->_deferred, &promise);
235
+ REJECT_RETURN;
236
+
237
+ size_t argc = 1;
238
+ napi_value args[1];
239
+ c->status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
240
+ REJECT_RETURN;
241
+
242
+ if (argc != (size_t) 1) REJECT_ERROR_RETURN(
243
+ "Sender must be created with an object containing at least a 'name' property.",
244
+ GRANDIOSE_INVALID_ARGS);
245
+
246
+ c->status = napi_typeof(env, args[0], &type);
247
+ REJECT_RETURN;
248
+ bool isArray;
249
+ c->status = napi_is_array(env, args[0], &isArray);
250
+ REJECT_RETURN;
251
+ if ((type != napi_object) || isArray) REJECT_ERROR_RETURN(
252
+ "Single argument must be an object, not an array, containing at least a 'name' property.",
253
+ GRANDIOSE_INVALID_ARGS);
254
+
255
+ napi_value config = args[0];
256
+ // napi_value name, groups, clockVideo, clockAudio;
257
+ napi_value name, clockVideo, clockAudio;
258
+
259
+ c->status = napi_get_named_property(env, config, "name", &name);
260
+ REJECT_RETURN;
261
+ c->status = napi_typeof(env, name, &type);
262
+ REJECT_RETURN;
263
+ if (type != napi_string) REJECT_ERROR_RETURN(
264
+ "Name property must be of type string.",
265
+ GRANDIOSE_INVALID_ARGS);
266
+ size_t namel;
267
+ c->status = napi_get_value_string_utf8(env, name, nullptr, 0, &namel);
268
+ REJECT_RETURN;
269
+ c->name = (char *) malloc(namel + 1);
270
+ c->status = napi_get_value_string_utf8(env, name, c->name, namel + 1, &namel);
271
+ REJECT_RETURN;
272
+
273
+ // c->status = napi_get_named_property(env, config, "groups", &groups);
274
+ // REJECT_RETURN;
275
+ // c->status = napi_typeof(env, groups, &type);
276
+ // REJECT_RETURN;
277
+
278
+ // if (type != napi_undefined && type != napi_string) REJECT_ERROR_RETURN(
279
+ // "Groups must be of type string or ....",
280
+ // GRANDIOSE_INVALID_ARGS);
281
+
282
+ c->status = napi_get_named_property(env, config, "clockVideo", &clockVideo);
283
+ REJECT_RETURN;
284
+ c->status = napi_typeof(env, clockVideo, &type);
285
+ REJECT_RETURN;
286
+ if (type != napi_undefined) {
287
+ if (type != napi_boolean) REJECT_ERROR_RETURN(
288
+ "ClockVideo property must be of type boolean.",
289
+ GRANDIOSE_INVALID_ARGS);
290
+ c->status = napi_get_value_bool(env, clockVideo, &c->clockVideo);
291
+ REJECT_RETURN;
292
+ }
293
+
294
+ c->status = napi_get_named_property(env, config, "clockAudio", &clockAudio);
295
+ REJECT_RETURN;
296
+ c->status = napi_typeof(env, clockAudio, &type);
297
+ REJECT_RETURN;
298
+ if (type != napi_undefined) {
299
+ if (type != napi_boolean) REJECT_ERROR_RETURN(
300
+ "ClockAudio property must be of type boolean.",
301
+ GRANDIOSE_INVALID_ARGS);
302
+ c->status = napi_get_value_bool(env, clockAudio, &c->clockAudio);
303
+ REJECT_RETURN;
304
+ }
305
+
306
+ napi_value resource_name;
307
+ c->status = napi_create_string_utf8(env, "Send", NAPI_AUTO_LENGTH, &resource_name);
308
+ REJECT_RETURN;
309
+ c->status = napi_create_async_work(env, NULL, resource_name, sendExecute,
310
+ sendComplete, c, &c->_request);
311
+ REJECT_RETURN;
312
+ c->status = napi_queue_async_work(env, c->_request);
313
+ REJECT_RETURN;
314
+
315
+ return promise;
316
+ }
317
+
318
+
319
+ void videoSendExecute(napi_env env, void* data) {
320
+ sendDataCarrier* c = (sendDataCarrier*) data;
321
+
322
+ NDIlib_send_send_video_v2(c->send, &c->videoFrame);
323
+ }
324
+
325
+ void videoSendComplete(napi_env env, napi_status asyncStatus, void* data) {
326
+ sendDataCarrier* c = (sendDataCarrier*) data;
327
+ napi_value result;
328
+ napi_status status;
329
+
330
+ c->status = napi_delete_reference(env, c->sourceBufferRef);
331
+ REJECT_STATUS;
332
+
333
+ if (asyncStatus != napi_ok) {
334
+ c->status = asyncStatus;
335
+ c->errorMsg = "Async video frame receive failed to complete.";
336
+ }
337
+ REJECT_STATUS;
338
+
339
+ c->status = napi_create_object(env, &result);
340
+ REJECT_STATUS;
341
+ status = napi_resolve_deferred(env, c->_deferred, result);
342
+ FLOATING_STATUS;
343
+
344
+ tidyCarrier(env, c);
345
+ }
346
+
347
+ napi_value videoSend(napi_env env, napi_callback_info info) {
348
+ napi_valuetype type;
349
+ sendDataCarrier* c = new sendDataCarrier;
350
+
351
+ napi_value promise;
352
+ c->status = napi_create_promise(env, &c->_deferred, &promise);
353
+ REJECT_RETURN;
354
+
355
+ size_t argc = 1;
356
+ napi_value args[1];
357
+ napi_value thisValue;
358
+ c->status = napi_get_cb_info(env, info, &argc, args, &thisValue, nullptr);
359
+ REJECT_RETURN;
360
+
361
+ napi_value sendValue;
362
+ c->status = napi_get_named_property(env, thisValue, "embedded", &sendValue);
363
+ REJECT_RETURN;
364
+ void* sendData;
365
+ c->status = napi_get_value_external(env, sendValue, &sendData);
366
+ c->send = (NDIlib_send_instance_t) sendData;
367
+ REJECT_RETURN;
368
+
369
+ if (argc >= 1) {
370
+ napi_value config;
371
+ config = args[0];
372
+ c->status = napi_typeof(env, config, &type);
373
+ REJECT_RETURN;
374
+ if (type != napi_object) REJECT_ERROR_RETURN(
375
+ "frame must be an object",
376
+ GRANDIOSE_INVALID_ARGS);
377
+
378
+ bool isArray, isBuffer;
379
+ c->status = napi_is_array(env, config, &isArray);
380
+ REJECT_RETURN;
381
+ if (isArray) REJECT_ERROR_RETURN(
382
+ "Argument to video send cannot be an array.",
383
+ GRANDIOSE_INVALID_ARGS);
384
+
385
+ napi_value param;
386
+ c->status = napi_get_named_property(env, config, "xres", &param);
387
+ REJECT_RETURN;
388
+ c->status = napi_typeof(env, param, &type);
389
+ REJECT_RETURN;
390
+ if (type != napi_number) REJECT_ERROR_RETURN(
391
+ "yres value must be a number",
392
+ GRANDIOSE_INVALID_ARGS);
393
+ c->status = napi_get_value_int32(env, param, &c->videoFrame.xres);
394
+ REJECT_RETURN;
395
+
396
+ c->status = napi_get_named_property(env, config, "yres", &param);
397
+ REJECT_RETURN;
398
+ c->status = napi_typeof(env, param, &type);
399
+ REJECT_RETURN;
400
+ if (type != napi_number) REJECT_ERROR_RETURN(
401
+ "yres value must be a number",
402
+ GRANDIOSE_INVALID_ARGS);
403
+ c->status = napi_get_value_int32(env, param, &c->videoFrame.yres);
404
+ REJECT_RETURN;
405
+
406
+ c->status = napi_get_named_property(env, config, "frameRateN", &param);
407
+ REJECT_RETURN;
408
+ c->status = napi_typeof(env, param, &type);
409
+ REJECT_RETURN;
410
+ if (type != napi_number) REJECT_ERROR_RETURN(
411
+ "frameRateN value must be a number",
412
+ GRANDIOSE_INVALID_ARGS);
413
+ c->status = napi_get_value_int32(env, param, &c->videoFrame.frame_rate_N);
414
+ REJECT_RETURN;
415
+
416
+ c->status = napi_get_named_property(env, config, "frameRateD", &param);
417
+ REJECT_RETURN;
418
+ c->status = napi_typeof(env, param, &type);
419
+ REJECT_RETURN;
420
+ if (type != napi_number) REJECT_ERROR_RETURN(
421
+ "frameRateD value must be a number",
422
+ GRANDIOSE_INVALID_ARGS);
423
+ c->status = napi_get_value_int32(env, param, &c->videoFrame.frame_rate_D);
424
+ REJECT_RETURN;
425
+
426
+ c->status = napi_get_named_property(env, config, "pictureAspectRatio", &param);
427
+ REJECT_RETURN;
428
+ c->status = napi_typeof(env, param, &type);
429
+ REJECT_RETURN;
430
+ if (type != napi_number) REJECT_ERROR_RETURN(
431
+ "pictureAspectRatio value must be a number",
432
+ GRANDIOSE_INVALID_ARGS);
433
+ double pictureAspectRatio;
434
+ c->status = napi_get_value_double(env, param, &pictureAspectRatio);
435
+ REJECT_RETURN;
436
+ c->videoFrame.picture_aspect_ratio = (float) pictureAspectRatio;
437
+
438
+ c->videoFrame.timecode = NDIlib_send_timecode_synthesize;
439
+ c->status = napi_get_named_property(env, config, "timecode", &param);
440
+ REJECT_RETURN;
441
+ c->status = napi_typeof(env, param, &type);
442
+ REJECT_RETURN;
443
+ if (type != napi_undefined) {
444
+ if (type == napi_number) {
445
+ c->status = napi_get_value_int64(env, param, &c->videoFrame.timecode);
446
+ REJECT_RETURN;
447
+ }
448
+ else if (type == napi_bigint) {
449
+ bool lossless;
450
+ c->status = napi_get_value_bigint_int64(env, param, &c->videoFrame.timecode, &lossless);
451
+ REJECT_RETURN;
452
+ }
453
+ else
454
+ REJECT_ERROR_RETURN("timecode value must be a number or bigint", GRANDIOSE_INVALID_ARGS);
455
+ }
456
+
457
+ /* initialize also timestamp (receiver-side only) and metadata */
458
+ c->videoFrame.timestamp = 0;
459
+ c->videoFrame.p_metadata = NULL;
460
+
461
+ c->status = napi_get_named_property(env, config, "frameFormatType", &param);
462
+ REJECT_RETURN;
463
+ c->status = napi_typeof(env, param, &type);
464
+ REJECT_RETURN;
465
+ if (type != napi_number) REJECT_ERROR_RETURN(
466
+ "frameFormatType value must be a number",
467
+ GRANDIOSE_INVALID_ARGS);
468
+ int32_t formatType;
469
+ c->status = napi_get_value_int32(env, param, &formatType);
470
+ REJECT_RETURN;
471
+ // TODO: checks
472
+ c->videoFrame.frame_format_type = (NDIlib_frame_format_type_e) formatType;
473
+
474
+ c->status = napi_get_named_property(env, config, "lineStrideBytes", &param);
475
+ REJECT_RETURN;
476
+ c->status = napi_typeof(env, param, &type);
477
+ REJECT_RETURN;
478
+ if (type != napi_number) REJECT_ERROR_RETURN(
479
+ "lineStrideBytes value must be a number",
480
+ GRANDIOSE_INVALID_ARGS);
481
+ c->status = napi_get_value_int32(env, param, &c->videoFrame.line_stride_in_bytes);
482
+ REJECT_RETURN;
483
+
484
+ napi_value videoBuffer;
485
+ c->status = napi_get_named_property(env, config, "data", &videoBuffer);
486
+ REJECT_RETURN;
487
+ c->status = napi_is_buffer(env, videoBuffer, &isBuffer);
488
+ REJECT_RETURN;
489
+ if (!isBuffer) REJECT_ERROR_RETURN(
490
+ "data must be provided as a Node Buffer",
491
+ GRANDIOSE_INVALID_ARGS);
492
+ void * data;
493
+ size_t length;
494
+ c->status = napi_get_buffer_info(env, videoBuffer, &data, &length);
495
+ REJECT_RETURN;
496
+ c->videoFrame.p_data = (uint8_t*) data;
497
+ c->status = napi_create_reference(env, videoBuffer, 1, &c->sourceBufferRef);
498
+ REJECT_RETURN;
499
+ // TODO: check length
500
+
501
+
502
+ c->status = napi_get_named_property(env, config, "fourCC", &param);
503
+ REJECT_RETURN;
504
+ c->status = napi_typeof(env, param, &type);
505
+ REJECT_RETURN;
506
+ if (type != napi_number) REJECT_ERROR_RETURN(
507
+ "fourCC value must be a number",
508
+ GRANDIOSE_INVALID_ARGS);
509
+ int32_t fourCC;
510
+ c->status = napi_get_value_int32(env, param, &fourCC);
511
+ REJECT_RETURN;
512
+ // TODO: checks
513
+ c->videoFrame.FourCC = (NDIlib_FourCC_video_type_e) fourCC; // TODO
514
+
515
+ } else REJECT_ERROR_RETURN(
516
+ "frame not provided",
517
+ GRANDIOSE_INVALID_ARGS);
518
+
519
+ napi_value resource_name;
520
+ c->status = napi_create_string_utf8(env, "VideoSend", NAPI_AUTO_LENGTH, &resource_name);
521
+ REJECT_RETURN;
522
+ c->status = napi_create_async_work(env, NULL, resource_name, videoSendExecute,
523
+ videoSendComplete, c, &c->_request);
524
+ REJECT_RETURN;
525
+ c->status = napi_queue_async_work(env, c->_request);
526
+ REJECT_RETURN;
527
+
528
+ return promise;
529
+ }
530
+
531
+ void audioSendExecute(napi_env env, void* data) {
532
+ sendDataCarrier* c = (sendDataCarrier*) data;
533
+
534
+ NDIlib_send_send_audio_v3(c->send, &c->audioFrame);
535
+ }
536
+
537
+ void audioSendComplete(napi_env env, napi_status asyncStatus, void* data) {
538
+ sendDataCarrier* c = (sendDataCarrier*) data;
539
+ napi_value result;
540
+ napi_status status;
541
+
542
+ c->status = napi_delete_reference(env, c->sourceBufferRef);
543
+ REJECT_STATUS;
544
+
545
+ if (asyncStatus != napi_ok) {
546
+ c->status = asyncStatus;
547
+ c->errorMsg = "Async audio frame send failed to complete.";
548
+ }
549
+ REJECT_STATUS;
550
+
551
+ c->status = napi_create_object(env, &result);
552
+ REJECT_STATUS;
553
+ status = napi_resolve_deferred(env, c->_deferred, result);
554
+ FLOATING_STATUS;
555
+
556
+ tidyCarrier(env, c);
557
+ }
558
+
559
+ napi_value audioSend(napi_env env, napi_callback_info info) {
560
+ napi_valuetype type;
561
+ sendDataCarrier* c = new sendDataCarrier;
562
+
563
+ napi_value promise;
564
+ c->status = napi_create_promise(env, &c->_deferred, &promise);
565
+ REJECT_RETURN;
566
+
567
+ size_t argc = 1;
568
+ napi_value args[1];
569
+ napi_value thisValue;
570
+ c->status = napi_get_cb_info(env, info, &argc, args, &thisValue, nullptr);
571
+ REJECT_RETURN;
572
+
573
+ napi_value sendValue;
574
+ c->status = napi_get_named_property(env, thisValue, "embedded", &sendValue);
575
+ REJECT_RETURN;
576
+ void* sendData;
577
+ c->status = napi_get_value_external(env, sendValue, &sendData);
578
+ c->send = (NDIlib_send_instance_t) sendData;
579
+ REJECT_RETURN;
580
+
581
+ if (argc >= 1) {
582
+ napi_value config;
583
+ config = args[0];
584
+ c->status = napi_typeof(env, config, &type);
585
+ REJECT_RETURN;
586
+ if (type != napi_object) REJECT_ERROR_RETURN(
587
+ "frame must be an object",
588
+ GRANDIOSE_INVALID_ARGS);
589
+
590
+ bool isArray, isBuffer;
591
+ c->status = napi_is_array(env, config, &isArray);
592
+ REJECT_RETURN;
593
+ if (isArray) REJECT_ERROR_RETURN(
594
+ "Argument to audio send cannot be an array.",
595
+ GRANDIOSE_INVALID_ARGS);
596
+
597
+ napi_value param;
598
+ c->status = napi_get_named_property(env, config, "sampleRate", &param);
599
+ REJECT_RETURN;
600
+ c->status = napi_typeof(env, param, &type);
601
+ REJECT_RETURN;
602
+ if (type != napi_number) REJECT_ERROR_RETURN(
603
+ "sampleRate value must be a number",
604
+ GRANDIOSE_INVALID_ARGS);
605
+ c->status = napi_get_value_int32(env, param, &c->audioFrame.sample_rate);
606
+ REJECT_RETURN;
607
+
608
+ c->status = napi_get_named_property(env, config, "noChannels", &param);
609
+ REJECT_RETURN;
610
+ c->status = napi_typeof(env, param, &type);
611
+ REJECT_RETURN;
612
+ if (type != napi_number) REJECT_ERROR_RETURN(
613
+ "noChannels value must be a number",
614
+ GRANDIOSE_INVALID_ARGS);
615
+ c->status = napi_get_value_int32(env, param, &c->audioFrame.no_channels);
616
+ REJECT_RETURN;
617
+
618
+ c->status = napi_get_named_property(env, config, "noSamples", &param);
619
+ REJECT_RETURN;
620
+ c->status = napi_typeof(env, param, &type);
621
+ REJECT_RETURN;
622
+ if (type != napi_number) REJECT_ERROR_RETURN(
623
+ "Samples value must be a number",
624
+ GRANDIOSE_INVALID_ARGS);
625
+ c->status = napi_get_value_int32(env, param, &c->audioFrame.no_samples);
626
+ REJECT_RETURN;
627
+
628
+ c->audioFrame.timecode = NDIlib_send_timecode_synthesize;
629
+ c->status = napi_get_named_property(env, config, "timecode", &param);
630
+ REJECT_RETURN;
631
+ c->status = napi_typeof(env, param, &type);
632
+ REJECT_RETURN;
633
+ if (type != napi_undefined) {
634
+ if (type == napi_number) {
635
+ c->status = napi_get_value_int64(env, param, &c->audioFrame.timecode);
636
+ REJECT_RETURN;
637
+ }
638
+ else if (type == napi_bigint) {
639
+ bool lossless;
640
+ c->status = napi_get_value_bigint_int64(env, param, &c->audioFrame.timecode, &lossless);
641
+ REJECT_RETURN;
642
+ }
643
+ else
644
+ REJECT_ERROR_RETURN("timecode value must be a number or bigint", GRANDIOSE_INVALID_ARGS);
645
+ }
646
+
647
+ /* initialize also timestamp (receiver-side only) and metadata */
648
+ c->audioFrame.timestamp = 0;
649
+ c->audioFrame.p_metadata = NULL;
650
+
651
+ c->status = napi_get_named_property(env, config, "channelStrideBytes", &param);
652
+ REJECT_RETURN;
653
+ c->status = napi_typeof(env, param, &type);
654
+ REJECT_RETURN;
655
+ if (type != napi_number) REJECT_ERROR_RETURN(
656
+ "channelStrideBytes value must be a number",
657
+ GRANDIOSE_INVALID_ARGS);
658
+ c->status = napi_get_value_int32(env, param, &c->audioFrame.channel_stride_in_bytes);
659
+ REJECT_RETURN;
660
+
661
+ napi_value audioBuffer;
662
+ c->status = napi_get_named_property(env, config, "data", &audioBuffer);
663
+ REJECT_RETURN;
664
+ c->status = napi_is_buffer(env, audioBuffer, &isBuffer);
665
+ REJECT_RETURN;
666
+ if (!isBuffer) REJECT_ERROR_RETURN(
667
+ "data must be provided as a Node Buffer",
668
+ GRANDIOSE_INVALID_ARGS);
669
+ void * data;
670
+ size_t length;
671
+ c->status = napi_get_buffer_info(env, audioBuffer, &data, &length);
672
+ REJECT_RETURN;
673
+ c->audioFrame.p_data = (uint8_t *) data;
674
+ c->status = napi_create_reference(env, audioBuffer, 1, &c->sourceBufferRef);
675
+ REJECT_RETURN;
676
+
677
+ c->status = napi_get_named_property(env, config, "fourCC", &param);
678
+ REJECT_RETURN;
679
+ c->status = napi_typeof(env, param, &type);
680
+ REJECT_RETURN;
681
+ if (type != napi_number) REJECT_ERROR_RETURN(
682
+ "fourCC value must be a number",
683
+ GRANDIOSE_INVALID_ARGS);
684
+ int32_t fourCC;
685
+ c->status = napi_get_value_int32(env, param, &fourCC);
686
+ REJECT_RETURN;
687
+ c->audioFrame.FourCC = (NDIlib_FourCC_audio_type_e)fourCC;
688
+
689
+ } else REJECT_ERROR_RETURN(
690
+ "frame not provided",
691
+ GRANDIOSE_INVALID_ARGS);
692
+
693
+ napi_value resource_name;
694
+ c->status = napi_create_string_utf8(env, "AudioSend", NAPI_AUTO_LENGTH, &resource_name);
695
+ REJECT_RETURN;
696
+ c->status = napi_create_async_work(env, NULL, resource_name, audioSendExecute,
697
+ audioSendComplete, c, &c->_request);
698
+ REJECT_RETURN;
699
+ c->status = napi_queue_async_work(env, c->_request);
700
+ REJECT_RETURN;
701
+
702
+ return promise;
703
+ }
704
+
705
+ napi_value connections(napi_env env, napi_callback_info info) {
706
+ napi_status status;
707
+
708
+ size_t argc = 1;
709
+ napi_value args[1];
710
+ napi_value thisValue;
711
+ status = napi_get_cb_info(env, info, &argc, args, &thisValue, nullptr);
712
+ CHECK_STATUS;
713
+
714
+ napi_value sendValue;
715
+ status = napi_get_named_property(env, thisValue, "embedded", &sendValue);
716
+ CHECK_STATUS;
717
+ void *sendData;
718
+ status = napi_get_value_external(env, sendValue, &sendData);
719
+ CHECK_STATUS;
720
+ NDIlib_send_instance_t sender = (NDIlib_send_instance_t)sendData;
721
+
722
+ int conns = NDIlib_send_get_no_connections(sender, 0);
723
+ napi_value result;
724
+ status = napi_create_int32(env, (int32_t)conns, &result);
725
+ CHECK_STATUS;
726
+
727
+ return result;
728
+ }
729
+
730
+ napi_value tally(napi_env env, napi_callback_info info) {
731
+ napi_status status;
732
+
733
+ size_t argc = 1;
734
+ napi_value args[1];
735
+ napi_value thisValue;
736
+ status = napi_get_cb_info(env, info, &argc, args, &thisValue, nullptr);
737
+ CHECK_STATUS;
738
+
739
+ napi_value sendValue;
740
+ status = napi_get_named_property(env, thisValue, "embedded", &sendValue);
741
+ CHECK_STATUS;
742
+ void *sendData;
743
+ status = napi_get_value_external(env, sendValue, &sendData);
744
+ CHECK_STATUS;
745
+ NDIlib_send_instance_t sender = (NDIlib_send_instance_t)sendData;
746
+
747
+ NDIlib_tally_t tally;
748
+ bool changed = NDIlib_send_get_tally(sender, &tally, 0);
749
+
750
+ napi_value result;
751
+ status = napi_create_object(env, &result);
752
+ CHECK_STATUS;
753
+
754
+ napi_value value;
755
+ napi_get_boolean(env, changed, &value);
756
+ status = napi_set_named_property(env, result, "changed", value);
757
+ CHECK_STATUS;
758
+ napi_get_boolean(env, tally.on_program, &value);
759
+ status = napi_set_named_property(env, result, "on_program", value);
760
+ CHECK_STATUS;
761
+ napi_get_boolean(env, tally.on_preview, &value);
762
+ status = napi_set_named_property(env, result, "on_preview", value);
763
+ CHECK_STATUS;
764
+
765
+ return result;
766
+ }
767
+
768
+ napi_value sourcename(napi_env env, napi_callback_info info) {
769
+ napi_status status;
770
+
771
+ size_t argc = 1;
772
+ napi_value args[1];
773
+ napi_value thisValue;
774
+ status = napi_get_cb_info(env, info, &argc, args, &thisValue, nullptr);
775
+ CHECK_STATUS;
776
+
777
+ napi_value sendValue;
778
+ status = napi_get_named_property(env, thisValue, "embedded", &sendValue);
779
+ CHECK_STATUS;
780
+ void *sendData;
781
+ status = napi_get_value_external(env, sendValue, &sendData);
782
+ CHECK_STATUS;
783
+ NDIlib_send_instance_t sender = (NDIlib_send_instance_t)sendData;
784
+
785
+ const NDIlib_source_t *source = NDIlib_send_get_source_name(sender);
786
+ napi_value result;
787
+ status = napi_create_string_utf8(env, source->p_ndi_name, NAPI_AUTO_LENGTH, &result);
788
+ CHECK_STATUS;
789
+
790
+ return result;
791
+ }
792
+