@4players/odin-nodejs 0.7.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.
Files changed (99) hide show
  1. package/.idea/.name +1 -0
  2. package/.idea/jsLibraryMappings.xml +6 -0
  3. package/.idea/misc.xml +4 -0
  4. package/.idea/modules.xml +9 -0
  5. package/.idea/odin-nodejs.iml +8 -0
  6. package/.idea/odin_nodejs.iml +2 -0
  7. package/.idea/php.xml +19 -0
  8. package/.idea/vcs.xml +6 -0
  9. package/CMakeLists.txt +25 -0
  10. package/README.md +53 -0
  11. package/binding.gyp +61 -0
  12. package/build/Makefile +352 -0
  13. package/build/Release/.deps/Release/nothing.a.d +1 -0
  14. package/build/Release/.deps/Release/obj.target/nothing/node_modules/node-addon-api/nothing.o.d +4 -0
  15. package/build/Release/.deps/Release/obj.target/odin/cppsrc/binding.o.d +27 -0
  16. package/build/Release/.deps/Release/obj.target/odin/cppsrc/odinbindings.o.d +24 -0
  17. package/build/Release/.deps/Release/obj.target/odin/cppsrc/odinclient.o.d +24 -0
  18. package/build/Release/.deps/Release/obj.target/odin/cppsrc/odinmedia.o.d +24 -0
  19. package/build/Release/.deps/Release/obj.target/odin/cppsrc/odinroom.o.d +24 -0
  20. package/build/Release/.deps/Release/obj.target/odin/cppsrc/utilities.o.d +21 -0
  21. package/build/Release/.deps/Release/odin.node.d +1 -0
  22. package/build/Release/nothing.a +0 -0
  23. package/build/Release/obj.target/nothing/node_modules/node-addon-api/nothing.o +0 -0
  24. package/build/Release/obj.target/odin/cppsrc/binding.o +0 -0
  25. package/build/Release/obj.target/odin/cppsrc/odinbindings.o +0 -0
  26. package/build/Release/obj.target/odin/cppsrc/odinclient.o +0 -0
  27. package/build/Release/obj.target/odin/cppsrc/odinmedia.o +0 -0
  28. package/build/Release/obj.target/odin/cppsrc/odinroom.o +0 -0
  29. package/build/Release/obj.target/odin/cppsrc/utilities.o +0 -0
  30. package/build/binding.Makefile +6 -0
  31. package/build/gyp-mac-tool +772 -0
  32. package/build/node_modules/node-addon-api/node_api.Makefile +6 -0
  33. package/build/node_modules/node-addon-api/nothing.target.mk +186 -0
  34. package/build/odin.target.mk +201 -0
  35. package/cmake-build-debug/.cmake/api/v1/query/cache-v2 +0 -0
  36. package/cmake-build-debug/.cmake/api/v1/query/cmakeFiles-v1 +0 -0
  37. package/cmake-build-debug/.cmake/api/v1/query/codemodel-v2 +0 -0
  38. package/cmake-build-debug/.cmake/api/v1/query/toolchains-v1 +0 -0
  39. package/cmake-build-debug/.cmake/api/v1/reply/cache-v2-912a6d0c3c2c7ebbadf8.json +1183 -0
  40. package/cmake-build-debug/.cmake/api/v1/reply/cmakeFiles-v1-0825651e80f1fb47890c.json +161 -0
  41. package/cmake-build-debug/.cmake/api/v1/reply/codemodel-v2-bc24edc239de864eb886.json +60 -0
  42. package/cmake-build-debug/.cmake/api/v1/reply/directory-.-Debug-f5ebdc15457944623624.json +14 -0
  43. package/cmake-build-debug/.cmake/api/v1/reply/index-2023-03-18T11-25-22-0179.json +108 -0
  44. package/cmake-build-debug/.cmake/api/v1/reply/target-odin_nodejs-Debug-1ac16802f252a85492c7.json +322 -0
  45. package/cmake-build-debug/.cmake/api/v1/reply/toolchains-v1-b57293a485ebf36c82cc.json +88 -0
  46. package/cmake-build-debug/.ninja_log +1 -0
  47. package/cmake-build-debug/CMakeCache.txt +367 -0
  48. package/cmake-build-debug/CMakeFiles/3.24.2/CMakeCCompiler.cmake +72 -0
  49. package/cmake-build-debug/CMakeFiles/3.24.2/CMakeCXXCompiler.cmake +83 -0
  50. package/cmake-build-debug/CMakeFiles/3.24.2/CMakeDetermineCompilerABI_C.bin +0 -0
  51. package/cmake-build-debug/CMakeFiles/3.24.2/CMakeDetermineCompilerABI_CXX.bin +0 -0
  52. package/cmake-build-debug/CMakeFiles/3.24.2/CMakeSystem.cmake +15 -0
  53. package/cmake-build-debug/CMakeFiles/3.24.2/CompilerIdC/CMakeCCompilerId.c +838 -0
  54. package/cmake-build-debug/CMakeFiles/3.24.2/CompilerIdC/CMakeCCompilerId.o +0 -0
  55. package/cmake-build-debug/CMakeFiles/3.24.2/CompilerIdCXX/CMakeCXXCompilerId.cpp +826 -0
  56. package/cmake-build-debug/CMakeFiles/3.24.2/CompilerIdCXX/CMakeCXXCompilerId.o +0 -0
  57. package/cmake-build-debug/CMakeFiles/CMakeError.log +22 -0
  58. package/cmake-build-debug/CMakeFiles/CMakeOutput.log +250 -0
  59. package/cmake-build-debug/CMakeFiles/TargetDirectories.txt +3 -0
  60. package/cmake-build-debug/CMakeFiles/clion-Debug-log.txt +4 -0
  61. package/cmake-build-debug/CMakeFiles/clion-environment.txt +3 -0
  62. package/cmake-build-debug/CMakeFiles/cmake.check_cache +1 -0
  63. package/cmake-build-debug/CMakeFiles/rules.ninja +74 -0
  64. package/cmake-build-debug/Testing/Temporary/LastTest.log +3 -0
  65. package/cmake-build-debug/build.ninja +205 -0
  66. package/cmake-build-debug/cmake_install.cmake +49 -0
  67. package/cppsrc/binding.cpp +15 -0
  68. package/cppsrc/odinbindings.cpp +58 -0
  69. package/cppsrc/odinbindings.h +9 -0
  70. package/cppsrc/odinclient.cpp +162 -0
  71. package/cppsrc/odinclient.h +17 -0
  72. package/cppsrc/odinmedia.cpp +242 -0
  73. package/cppsrc/odinmedia.h +35 -0
  74. package/cppsrc/odinroom.cpp +738 -0
  75. package/cppsrc/odinroom.h +65 -0
  76. package/cppsrc/utilities.cpp +102 -0
  77. package/cppsrc/utilities.h +175 -0
  78. package/index.cjs +2 -0
  79. package/index.d.ts +5 -0
  80. package/libs/bin/linux/arm64/libodin_static.a +0 -0
  81. package/libs/bin/linux/ia32/libodin_static.a +0 -0
  82. package/libs/bin/linux/x64/libodin_static.a +0 -0
  83. package/libs/bin/macos/arm64/libodin_static.a +0 -0
  84. package/libs/bin/macos/x64/libodin_static.a +0 -0
  85. package/libs/bin/windows/arm64/odin_static.lib +0 -0
  86. package/libs/bin/windows/ia32/odin_static.lib +0 -0
  87. package/libs/bin/windows/x64/odin_static.lib +0 -0
  88. package/libs/include/odin.h +896 -0
  89. package/odin.client.d.ts +22 -0
  90. package/odin.media.d.ts +33 -0
  91. package/odin.room.d.ts +473 -0
  92. package/package.json +50 -0
  93. package/prebuilds/darwin-arm64/node.napi.node +0 -0
  94. package/test.js +211 -0
  95. package/tests/audio-recording/README.md +24 -0
  96. package/tests/audio-recording/index.js +148 -0
  97. package/tests/sending-audio/README.md +20 -0
  98. package/tests/sending-audio/index.js +81 -0
  99. package/tests/sending-audio/santa.mp3 +0 -0
@@ -0,0 +1,738 @@
1
+ //
2
+ // Created by Phillip Schuster on 11.02.23.
3
+ //
4
+
5
+ #include "odinroom.h"
6
+ #include <string>
7
+ #include <iostream>
8
+ #include <codecvt>
9
+ #include <locale>
10
+ #include "odinmedia.h"
11
+
12
+ using namespace std;
13
+
14
+ // Required, otherwise an unknown symbol error comes up
15
+ Napi::FunctionReference* OdinRoom::constructor;
16
+
17
+ /**
18
+ * Prepares a NAPI value object for the event
19
+ * @param env
20
+ * @param event The event data
21
+ * @return
22
+ */
23
+ Napi::Object OdinRoom::PrepareEventObject(Napi::Env env, const EventData *event) {
24
+ Napi::Object obj = Napi::Object::New(env);
25
+ obj.Set("event", event->Event);
26
+ obj.Set("tag", (int)event->Tag);
27
+ switch(event->Tag) {
28
+ case OdinEvent_Joined: {
29
+ const JoinedEventData *joined = static_cast<const JoinedEventData*>(event);
30
+ obj.Set("roomId", joined->RoomId);
31
+ obj.Set("ownPeerId", joined->PeerId);
32
+ obj.Set("ownUserId", joined->UserId);
33
+ Napi::ArrayBuffer buffer = Napi::ArrayBuffer::New(env, (void*)joined->Data, joined->DataLen);
34
+ Napi::Uint8Array array = Napi::Uint8Array::New(env, joined->DataLen, buffer, 0, napi_uint8_array);
35
+ obj.Set("roomUserData", array);
36
+ break;
37
+ }
38
+ case OdinEvent_PeerJoined: {
39
+ const PeerJoinedEventData *joined = static_cast<const PeerJoinedEventData*>(event);
40
+ obj.Set("peerId", joined->PeerId);
41
+ obj.Set("userId", joined->UserId);
42
+ Napi::ArrayBuffer buffer = Napi::ArrayBuffer::New(env, (void*)joined->Data, joined->DataLen);
43
+ Napi::Uint8Array array = Napi::Uint8Array::New(env, joined->DataLen, buffer, 0, napi_uint8_array);
44
+ obj.Set("userData", array);
45
+ break;
46
+ }
47
+ case OdinEvent_PeerLeft: {
48
+ const PeerLeftEventData *left = static_cast<const PeerLeftEventData*>(event);
49
+ obj.Set("peerId", left->PeerId);
50
+ break;
51
+ }
52
+ case OdinEvent_PeerUserDataChanged: {
53
+ const PeerUserDataChangedEventData *changed = static_cast<const PeerUserDataChangedEventData*>(event);
54
+ obj.Set("peerId", changed->PeerId);
55
+ Napi::ArrayBuffer buffer = Napi::ArrayBuffer::New(env, (void*)changed->Data, changed->DataLen);
56
+ Napi::Uint8Array array = Napi::Uint8Array::New(env, changed->DataLen, buffer, 0, napi_uint8_array);
57
+ obj.Set("userData", array);
58
+ break;
59
+ }
60
+ case OdinEvent_RoomUserDataChanged: {
61
+ const RoomUserDataChangedEventData *changed = static_cast<const RoomUserDataChangedEventData*>(event);
62
+ Napi::ArrayBuffer buffer = Napi::ArrayBuffer::New(env, (void*)changed->Data, changed->DataLen);
63
+ Napi::Uint8Array array = Napi::Uint8Array::New(env, changed->DataLen, buffer, 0, napi_uint8_array);
64
+ obj.Set("userData", array);
65
+ break;
66
+ }
67
+ case OdinEvent_MessageReceived: {
68
+ const MessageReceivedEventData *message = static_cast<const MessageReceivedEventData*>(event);
69
+ obj.Set("peerId", message->PeerId);
70
+ Napi::ArrayBuffer buffer = Napi::ArrayBuffer::New(env, (void*)message->Data, message->DataLen);
71
+ Napi::Uint8Array array = Napi::Uint8Array::New(env, message->DataLen, buffer, 0, napi_uint8_array);
72
+ obj.Set("messageData", array);
73
+ break;
74
+ }
75
+ case OdinEvent_RoomConnectionStateChanged: {
76
+ const RoomConnectionStateChangedEventData *state = static_cast<const RoomConnectionStateChangedEventData*>(event);
77
+ obj.Set("state", state->StateName);
78
+ obj.Set("reason", state->ReasonName);
79
+ break;
80
+ }
81
+ case OdinEvent_MediaAdded: {
82
+ const MediaAddedEventData *added = static_cast<const MediaAddedEventData*>(event);
83
+ obj.Set("peerId", added->PeerId);
84
+ obj.Set("mediaId", added->MediaId);
85
+ break;
86
+ }
87
+ case OdinEvent_MediaRemoved: {
88
+ const MediaRemovedEventData *removed = static_cast<const MediaRemovedEventData*>(event);
89
+ obj.Set("peerId", removed->PeerId);
90
+ obj.Set("mediaId", removed->MediaId);
91
+ break;
92
+ }
93
+ case OdinEvent_MediaActiveStateChanged: {
94
+ const MediaActiveStateChangedEventData *activity = static_cast<const MediaActiveStateChangedEventData*>(event);
95
+ obj.Set("peerId", activity->PeerId);
96
+ obj.Set("mediaId", activity->MediaId);
97
+ obj.Set("state", activity->Active);
98
+ break;
99
+ }
100
+ }
101
+
102
+ return obj;
103
+ }
104
+
105
+ /**
106
+ * Returns the room id as string of this room instance
107
+ * @param info
108
+ */
109
+ Napi::Value OdinRoom::RoomId(const Napi::CallbackInfo &info) {
110
+ Napi::Env env = info.Env();
111
+
112
+ char out_id[512];
113
+ OdinReturnCode error = odin_room_id(_roomHandle, out_id, sizeof(out_id));
114
+ if (odin_is_error(error))
115
+ {
116
+ Napi::TypeError::New(env, "Failed to get room id").ThrowAsJavaScriptException();
117
+ }
118
+
119
+ return Napi::String::New(env, out_id);
120
+ }
121
+
122
+ /**
123
+ * Returns the room handle of this room instance
124
+ */
125
+ OdinRoomHandle OdinRoom::GetRoomHandle() const {
126
+ return _roomHandle;
127
+ }
128
+
129
+ /**
130
+ * Returns the media id of a media stream handle, used internally to map media stream handles to media ids in event callbacks
131
+ */
132
+ uint16_t OdinRoom::GetMediaIdFromHandle(OdinMediaStreamHandle handle)
133
+ {
134
+ uint16_t media_id;
135
+ int error = odin_media_stream_media_id(handle, &media_id);
136
+ return odin_is_error(error) ? 0 : media_id;
137
+ }
138
+
139
+ /**
140
+ * Creates an EventData object instance from an OdinEvent. As OdinEvents are allocated on the Odin SDK side, we need to
141
+ * copy the data as the callbacks to the JS side are asynchronous.
142
+ * @param event
143
+ * @return EventData
144
+ */
145
+ EventData* OdinRoom::PrepareEventData(OdinEvent* event)
146
+ {
147
+ switch(event->tag) {
148
+ case OdinEvent_Joined: {
149
+ JoinedEventData* data = new JoinedEventData();
150
+ data->Tag = event->tag;
151
+ data->Event = "Joined";
152
+ data->RoomId = event->joined.room_id;
153
+ data->UserId = event->joined.own_user_id;
154
+ data->PeerId = event->joined.own_peer_id;
155
+ data->SetData(event->joined.room_user_data, event->joined.room_user_data_len);
156
+ return data;
157
+ }
158
+ case OdinEvent_PeerJoined: {
159
+ PeerJoinedEventData* data = new PeerJoinedEventData();
160
+ data->Tag = event->tag;
161
+ data->Event = "PeerJoined";
162
+ data->PeerId = event->peer_joined.peer_id;
163
+ data->UserId = event->peer_joined.user_id;
164
+ data->SetData(event->peer_joined.peer_user_data, event->peer_joined.peer_user_data_len);
165
+ return data;
166
+ }
167
+ case OdinEvent_PeerLeft: {
168
+ PeerLeftEventData* data = new PeerLeftEventData();
169
+ data->Tag = event->tag;
170
+ data->Event = "PeerLeft";
171
+ data->PeerId = event->peer_left.peer_id;
172
+ return data;
173
+ }
174
+ case OdinEvent_PeerUserDataChanged: {
175
+ PeerUserDataChangedEventData* data = new PeerUserDataChangedEventData();
176
+ data->Tag = event->tag;
177
+ data->Event = "PeerUserDataChanged";
178
+ data->PeerId = event->peer_user_data_changed.peer_id;
179
+ data->SetData(event->peer_user_data_changed.peer_user_data, event->peer_user_data_changed.peer_user_data_len);
180
+ return data;
181
+ }
182
+ case OdinEvent_RoomUserDataChanged: {
183
+ RoomUserDataChangedEventData* data = new RoomUserDataChangedEventData();
184
+ data->Tag = event->tag;
185
+ data->Event = "RoomUserDataChanged";
186
+ data->SetData(event->room_user_data_changed.room_user_data, event->room_user_data_changed.room_user_data_len);
187
+ return data;
188
+ }
189
+ case OdinEvent_MessageReceived: {
190
+ MessageReceivedEventData* data = new MessageReceivedEventData();
191
+ data->Tag = event->tag;
192
+ data->Event = "MessageReceived";
193
+ data->PeerId = event->message_received.peer_id;
194
+ data->SetData(event->message_received.data, event->message_received.data_len);
195
+ return data;
196
+ }
197
+ case OdinEvent_RoomConnectionStateChanged: {
198
+ RoomConnectionStateChangedEventData* data = new RoomConnectionStateChangedEventData();
199
+ data->Tag = event->tag;
200
+ data->Event = "ConnectionStateChanged";
201
+ data->State = (int)event->room_connection_state_changed.state;
202
+ data->Reason = (int)event->room_connection_state_changed.reason;
203
+ data->StateName = OdinUtilities::GetNameFromConnectionState(event->room_connection_state_changed.state);
204
+ data->ReasonName = OdinUtilities::GetNameFromConnectionStateChangeReason(event->room_connection_state_changed.reason);
205
+ return data;
206
+ }
207
+ case OdinEvent_MediaAdded: {
208
+ MediaAddedEventData* data = new MediaAddedEventData();
209
+ data->Tag = event->tag;
210
+ data->Event = "MediaAdded";
211
+ data->PeerId = event->media_added.peer_id;
212
+ data->MediaId = GetMediaIdFromHandle(event->media_added.media_handle);
213
+ data->MediaStreamHandle = event->media_added.media_handle;
214
+ return data;
215
+ }
216
+ case OdinEvent_MediaRemoved: {
217
+ MediaRemovedEventData* data = new MediaRemovedEventData();
218
+ data->Tag = event->tag;
219
+ data->Event = "MediaRemoved";
220
+ data->PeerId = event->media_removed.peer_id;
221
+ data->MediaId = GetMediaIdFromHandle(event->media_removed.media_handle);
222
+ return data;
223
+ }
224
+ case OdinEvent_MediaActiveStateChanged: {
225
+ MediaActiveStateChangedEventData *data = new MediaActiveStateChangedEventData();
226
+ data->Tag = event->tag;
227
+ data->Event = "MediaActivity";
228
+ data->PeerId = event->media_active_state_changed.peer_id;
229
+ data->MediaId = GetMediaIdFromHandle(event->media_active_state_changed.media_handle);
230
+ data->Active = event->media_active_state_changed.active;
231
+ return data;
232
+ }
233
+ default:
234
+ return NULL;
235
+ }
236
+
237
+ return NULL;
238
+ }
239
+
240
+ /**
241
+ * Callback function for Odin events. This function is called by the Odin SDK when an event occurs. It creates an EventData
242
+ * object instance from the OdinEvent and calls the JavaScript callback function with the event data.
243
+ * @param room OdinRoomHandle
244
+ * @param event OdinEvent
245
+ * @param data void* Pointer to the OdinRoom instance
246
+ */
247
+ void OdinRoom::HandleOdinEvent(OdinRoomHandle room, const OdinEvent *event, void *data)
248
+ {
249
+ OdinRoom* odinRoom = (OdinRoom*)data;
250
+ if (odinRoom != NULL) {
251
+
252
+ ::uint16_t mediaId = OdinRoom::GetMediaIdFromHandle(event->media_added.media_handle);
253
+ if (event->tag == OdinEvent_MediaAdded) {
254
+ Media media;
255
+ media.PeerId = event->media_added.peer_id;
256
+ media.Id = mediaId;
257
+ media.Handle = event->media_added.media_handle;
258
+ odinRoom->_mediaStreams[media.Id] = media;
259
+ } else if (event->tag == OdinEvent_MediaRemoved) {
260
+ if (odinRoom->_mediaStreams.find(mediaId) != odinRoom->_mediaStreams.end()) {
261
+ odinRoom->_mediaStreams.erase(mediaId);
262
+ }
263
+ }
264
+
265
+ auto callback = []( Napi::Env env, Napi::Function jsCallback, void* value ) {
266
+ EventData* eventData = (EventData*)value;
267
+ if (eventData == NULL) {
268
+ // I have no idea how to call a function without a parameter ;-)
269
+ // This should never happen anyway
270
+ jsCallback.Call({Napi::Number::New( env, 42 )});
271
+ } else {
272
+ Napi::Object obj = PrepareEventObject(env, eventData);
273
+ jsCallback.Call( {obj} );
274
+ delete eventData;
275
+ }
276
+ };
277
+ if (odinRoom->_eventListener != NULL)
278
+ {
279
+ EventData* data = odinRoom->PrepareEventData((OdinEvent*)event);
280
+ #ifdef DEBUG
281
+ printf("Odin NodeJS Addon: Sending event to JS: %s\n", data->Event.c_str());
282
+ #endif
283
+ odinRoom->_eventListener.BlockingCall( (void*)data, callback );
284
+ }
285
+
286
+ EventData* data = odinRoom->PrepareEventData((OdinEvent*)event);
287
+ Napi::ThreadSafeFunction function = odinRoom->_eventListeners[data->Event];
288
+ if (function != NULL) {
289
+ function.BlockingCall( (void*)data, callback );
290
+ }
291
+
292
+ } else {
293
+ printf("Odin NodeJS Addon: No room available when handling events - this should NEVER HAPPEN.\n");
294
+ }
295
+ }
296
+
297
+ /**
298
+ * Creates a new OdinRoom instance. Requires an Odin token as a parameter.
299
+ * @param info Napi::CallbackInfo
300
+ */
301
+ OdinRoom::OdinRoom(const Napi::CallbackInfo& info) :Napi::ObjectWrap<OdinRoom>(info) {
302
+ Napi::Env env = info.Env();
303
+
304
+ if (info.Length() != 1 || !info[0].IsString()) {
305
+ Napi::TypeError::New(env, "Provide a token to create a room").ThrowAsJavaScriptException();
306
+ }
307
+
308
+ _token = info[0].ToString().Utf8Value();
309
+ _roomHandle = odin_room_create();
310
+ _started = false;
311
+ _joined = false;
312
+
313
+ odin_room_set_event_callback(_roomHandle, OdinRoom::HandleOdinEvent, this);
314
+ }
315
+
316
+ /**
317
+ * Destructor. Closes and destroys the OdinRoom and resets the event callback for this room handle.
318
+ */
319
+ void OdinRoom::Finalize(Napi::Env env) {
320
+ odin_room_set_event_callback(_roomHandle, NULL, NULL);
321
+ odin_room_close(_roomHandle);
322
+ odin_room_destroy(_roomHandle);
323
+ }
324
+
325
+ /**
326
+ * Exposes this class to JavaScript
327
+ * @param info Napi::CallbackInfo
328
+ */
329
+ Napi::Object OdinRoom::Init(Napi::Env env, Napi::Object exports) {
330
+ // This method is used to hook the accessor and method callbacks
331
+ Napi::Function func = DefineClass(env, "OdinRoom", {
332
+ InstanceMethod<&OdinRoom::Join>("join", static_cast<napi_property_attributes>(napi_writable | napi_configurable)),
333
+ InstanceMethod<&OdinRoom::UpdatePeerUserData>("updateOwnUserData", static_cast<napi_property_attributes>(napi_writable | napi_configurable)),
334
+ InstanceMethod<&OdinRoom::UpdateRoomUserData>("updateRoomUserData", static_cast<napi_property_attributes>(napi_writable | napi_configurable)),
335
+ InstanceMethod<&OdinRoom::SendMessage>("sendMessage", static_cast<napi_property_attributes>(napi_writable | napi_configurable)),
336
+ InstanceMethod<&OdinRoom::SetEventListener>("setEventListener", static_cast<napi_property_attributes>(napi_writable | napi_configurable)),
337
+ InstanceMethod<&OdinRoom::AddEventListener>("addEventListener", static_cast<napi_property_attributes>(napi_writable | napi_configurable)),
338
+ InstanceMethod<&OdinRoom::RemoveEventListener>("removeEventListener", static_cast<napi_property_attributes>(napi_writable | napi_configurable)),
339
+ InstanceMethod<&OdinRoom::Close>("close", static_cast<napi_property_attributes>(napi_writable | napi_configurable)),
340
+ InstanceMethod<&OdinRoom::CreateAudioStream>("createAudioStream", static_cast<napi_property_attributes>(napi_writable | napi_configurable)),
341
+ InstanceAccessor("ownPeerId", &OdinRoom::GetOwnPeerId, nullptr, static_cast<napi_property_attributes>(napi_writable | napi_configurable)),
342
+ InstanceAccessor("id", &OdinRoom::RoomId, nullptr, static_cast<napi_property_attributes>(napi_writable | napi_configurable)),
343
+ StaticMethod<&OdinRoom::CreateNewItem>("CreateNewItem", static_cast<napi_property_attributes>(napi_writable | napi_configurable)),
344
+ });
345
+
346
+ // We use a static variable to store the constructor. This is because we need to create instances within C++ and
347
+ // expose to JavaScript. We can't use the constructor directly because it's not exposed to JavaScript.
348
+ constructor = new Napi::FunctionReference();
349
+
350
+ // Create a persistent reference to the class constructor. This will allow
351
+ // a function called on a class prototype and a function
352
+ // called on instance of a class to be distinguished from each other.
353
+ *constructor = Napi::Persistent(func);
354
+ exports.Set("OdinRoom", func);
355
+
356
+ // Store the constructor as the add-on instance data. This will allow this
357
+ // add-on to support multiple instances of itself running on multiple worker
358
+ // threads, as well as multiple instances of itself running in different
359
+ // contexts on the same thread.
360
+ //
361
+ // By default, the value set on the environment here will be destroyed when
362
+ // the add-on is unloaded using the `delete` operator, but it is also
363
+ // possible to supply a custom deleter.
364
+ env.SetInstanceData<Napi::FunctionReference>(constructor);
365
+
366
+ return exports;
367
+ }
368
+
369
+ /**
370
+ * Joins the Odin room. Requires a gateway URL as a parameter and optionally initial peer data.
371
+ * @param info Napi::CallbackInfo
372
+ */
373
+ void OdinRoom::Join(const Napi::CallbackInfo &info) {
374
+ Napi::Env env = info.Env();
375
+
376
+ if (info.Length() < 1) {
377
+ Napi::TypeError::New(env, "Gateway required as first parameter").ThrowAsJavaScriptException();
378
+ }
379
+
380
+ std::string url = info[0].ToString().Utf8Value();
381
+
382
+ if (info.Length() > 1) {
383
+ Napi::Uint8Array data = info[1].As<Napi::Uint8Array>();
384
+ OdinReturnCode error = odin_room_update_user_data(_roomHandle, OdinUserDataTarget_Peer, data.Data(), data.ByteLength());
385
+ if (odin_is_error(error))
386
+ {
387
+ Napi::TypeError::New(env, "Setting initial room peer data failed").ThrowAsJavaScriptException();
388
+ }
389
+ }
390
+
391
+ OdinReturnCode error = odin_room_join(_roomHandle, url.c_str(), _token.c_str());
392
+ if (odin_is_error(error))
393
+ {
394
+ OdinUtilities::ThrowNapiException(env, error, "Failed to join room");
395
+ }
396
+
397
+ _joined = true;
398
+
399
+ // Start capturing audio data if event listener has not been started yet
400
+ if (_audioDataReceivedEventListener) {
401
+ HandleAudioData();
402
+ }
403
+ }
404
+
405
+ /**
406
+ * Closes the room connection and destroys the room handle.
407
+ * @param info
408
+ */
409
+ void OdinRoom::Close(const Napi::CallbackInfo &info) {
410
+ //Napi::Env env = info.Env();
411
+
412
+ if (_roomHandle <= 0) return;
413
+
414
+ odin_room_set_event_callback(_roomHandle, NULL, NULL);
415
+
416
+ if (this->_eventListener != NULL) {
417
+ this->_eventListener.Release();
418
+ this->_eventListener = NULL;
419
+ }
420
+
421
+ for (auto it = _eventListeners.begin(); it != _eventListeners.end(); ++it) {
422
+ if (it->second != NULL) {
423
+ it->second.Release();
424
+ it->second = NULL;
425
+ }
426
+ }
427
+
428
+ odin_room_close(_roomHandle);
429
+ odin_room_destroy(_roomHandle);
430
+ _roomHandle = 0;
431
+ }
432
+
433
+ /**
434
+ * Creates a new instance of the OdinRoom class. Requires a token as a parameter.
435
+ * @param info
436
+ * @return
437
+ */
438
+ Napi::Value OdinRoom::CreateNewItem(const Napi::CallbackInfo& info) {
439
+ Napi::Env env = info.Env();
440
+
441
+ if (info.Length() < 1) {
442
+ Napi::TypeError::New(env, "Token required as first parameter").ThrowAsJavaScriptException();
443
+ }
444
+
445
+ Napi::String token = info[0].ToString();
446
+
447
+ // Retrieve the instance data we stored during `Init()`. We only stored the
448
+ // constructor there, so we retrieve it here to create a new instance of the
449
+ // JS class the constructor represents.
450
+ Napi::FunctionReference* constructor =
451
+ info.Env().GetInstanceData<Napi::FunctionReference>();
452
+ return constructor->New({ token });
453
+ }
454
+
455
+ /**
456
+ * Creates a new instance of the OdinRoom class. Requires a token as a parameter.
457
+ * @param info
458
+ * @return
459
+ */
460
+ Napi::Object OdinRoom::NewInstance(Napi::Value arg) {
461
+ return constructor->New({ arg });
462
+ }
463
+
464
+ /**
465
+ * Updates the peer user data. Requires an array of bytes as a parameter.
466
+ * @param info
467
+ * @return
468
+ */
469
+ void OdinRoom::UpdatePeerUserData(const Napi::CallbackInfo &info) {
470
+ Napi::Env env = info.Env();
471
+
472
+ if (info.Length() != 1 || !info[0].IsTypedArray()) {
473
+ Napi::TypeError::New(env, "Data as byte array expected").ThrowAsJavaScriptException();
474
+ }
475
+
476
+ Napi::Uint8Array data = info[0].As<Napi::Uint8Array>();
477
+
478
+ OdinReturnCode error = odin_room_update_user_data(_roomHandle, OdinUserDataTarget_Peer, data.Data(), data.ByteLength());
479
+
480
+ if (odin_is_error(error))
481
+ {
482
+ OdinUtilities::ThrowNapiException(env, error, "Failed to update peer user data");
483
+ }
484
+ }
485
+
486
+ /**
487
+ * Updates the room user data. Requires an array of bytes as a parameter.
488
+ * @param info
489
+ * @return
490
+ */
491
+ void OdinRoom::UpdateRoomUserData(const Napi::CallbackInfo &info) {
492
+ Napi::Env env = info.Env();
493
+
494
+ if (info.Length() != 1 || !info[0].IsTypedArray()) {
495
+ Napi::TypeError::New(env, "Data as byte array expected").ThrowAsJavaScriptException();
496
+ }
497
+
498
+ Napi::Uint8Array data = info[0].As<Napi::Uint8Array>();
499
+
500
+ OdinReturnCode error = odin_room_update_user_data(_roomHandle, OdinUserDataTarget_Room, data.Data(), data.ByteLength());
501
+
502
+ if (odin_is_error(error))
503
+ {
504
+ OdinUtilities::ThrowNapiException(env, error, "Failed to update room user data");
505
+ }
506
+ }
507
+
508
+ /**
509
+ * Sends a message to the room. Requires an array of bytes as a parameter and optinally a list of peer ids to send to.
510
+ * Please note: If the list is NULL the message will be sent to all peers in the room. If the list is not NULL the message
511
+ * will be sent to all peers in the list. If the list is empty the message ODIN will raise an error.
512
+ * @param info
513
+ * @return
514
+ */
515
+ void OdinRoom::SendMessage(const Napi::CallbackInfo &info) {
516
+ Napi::Env env = info.Env();
517
+
518
+ if (info.Length() < 1 || !info[0].IsTypedArray()) {
519
+ Napi::TypeError::New(env, "Data as byte array expected").ThrowAsJavaScriptException();
520
+ }
521
+
522
+ Napi::Uint8Array data = info[0].As<Napi::Uint8Array>();
523
+
524
+ uint64_t *peer_id_list = NULL;
525
+ size_t peer_id_list_length = 0;
526
+ if (info.Length() >= 2 && !info[1].IsNull() && !info[1].IsUndefined()) {
527
+ if (!info[1].IsArray()) {
528
+ Napi::TypeError::New(env, "Peer id list as uint32 array expected").ThrowAsJavaScriptException();
529
+ } else {
530
+ Napi::Array peerIds = info[1].As<Napi::Array>();
531
+ peer_id_list_length = peerIds.Length();
532
+ peer_id_list = new uint64_t[peer_id_list_length];
533
+ for (int i = 0; i < (int)peer_id_list_length; i++) {
534
+ peer_id_list[i] = peerIds.Get(i).As<Napi::Number>().Uint32Value();
535
+ }
536
+ }
537
+ }
538
+
539
+ OdinReturnCode error = odin_room_send_message(_roomHandle, peer_id_list, peer_id_list_length, data.Data(), data.ByteLength());
540
+
541
+ if (peer_id_list != NULL) {
542
+ delete[] peer_id_list;
543
+ }
544
+
545
+ if (odin_is_error(error))
546
+ {
547
+ OdinUtilities::ThrowNapiException(env, error, "Failed to update room user data");
548
+ }
549
+ }
550
+
551
+ /**
552
+ * Sets a global event listener for this room. Requires a callback function as a parameter and will receive all events
553
+ * @param info
554
+ * @return
555
+ */
556
+ void OdinRoom::SetEventListener(const Napi::CallbackInfo &info) {
557
+ Napi::Env env = info.Env();
558
+
559
+ if (info.Length() != 1 || !info[0].IsFunction()) {
560
+ Napi::TypeError::New(env, "Callback function expected").ThrowAsJavaScriptException();
561
+ }
562
+
563
+ Napi::Function napiFunction = info[0].As<Napi::Function>();
564
+
565
+ _eventListener = Napi::ThreadSafeFunction::New(env, napiFunction, "Callback", 0, 1);
566
+ }
567
+
568
+ /**
569
+ * Adds an event listener for this room. Requires the event type to listen to and a callback function as a second parameter
570
+ * @param info
571
+ * @return
572
+ */
573
+ void OdinRoom::AddEventListener(const Napi::CallbackInfo &info) {
574
+ Napi::Env env = info.Env();
575
+
576
+ if (info.Length() != 2 || !info[0].IsString() || !info[1].IsFunction()) {
577
+ Napi::TypeError::New(env, "Event Name and Callback function expected").ThrowAsJavaScriptException();
578
+ }
579
+
580
+ std::string eventName = info[0].As<Napi::String>().Utf8Value();
581
+ Napi::Function napiFunction = info[1].As<Napi::Function>();
582
+
583
+ if (eventName != "AudioDataReceived") {
584
+ _eventListeners[eventName] = Napi::ThreadSafeFunction::New(env, napiFunction, eventName + "Callback", 0, 1);
585
+ } else {
586
+ _audioDataReceivedEventListener = Napi::ThreadSafeFunction::New(
587
+ env,
588
+ napiFunction,
589
+ "AudioDataReceivedCallback",
590
+ 0,
591
+ 1,
592
+ [this](Napi::Env) {
593
+ // This is the finalizer thread
594
+ this->_nativeThread.join();
595
+ }
596
+ );
597
+
598
+ HandleAudioData();
599
+ }
600
+ }
601
+
602
+ /**
603
+ * Removes an event listener for this room. Requires the event type to remove the listener for.
604
+ * @param info
605
+ * @return
606
+ */
607
+ void OdinRoom::RemoveEventListener(const Napi::CallbackInfo &info) {
608
+ Napi::Env env = info.Env();
609
+
610
+ if (info.Length() != 1 || !info[0].IsString()) {
611
+ Napi::TypeError::New(env, "Event Name expected").ThrowAsJavaScriptException();
612
+ }
613
+
614
+ std::string eventName = info[0].As<Napi::String>().Utf8Value();
615
+
616
+ if (eventName != "AudioDataReceived") {
617
+ _eventListeners[eventName].Release();
618
+ _eventListeners.erase(eventName);
619
+ } else {
620
+ _audioDataReceivedEventListener.Release();
621
+ }
622
+ }
623
+
624
+ /**
625
+ * Internal function to receive audio data every 20ms and send it to the JS side. This function will start once the client
626
+ * has registered AudioDataReceived as an event listener.
627
+ */
628
+ void OdinRoom::HandleAudioData()
629
+ {
630
+ if (!_joined) {
631
+ #ifdef DEBUG
632
+ printf("Odin NodeJS Addon: Skipping audio until joined\n");
633
+ #endif
634
+ return;
635
+ }
636
+
637
+ if (_started) {
638
+ #ifdef DEBUG
639
+ printf("Odin NodeJS Addon: Handle Audio Data thread already started\n");
640
+ #endif
641
+ return;
642
+ }
643
+
644
+ _started = true;
645
+
646
+ // Create a native thread
647
+ _nativeThread = std::thread( [this] {
648
+ auto callback = []( Napi::Env env, Napi::Function jsCallback, void* value ) {
649
+ AudioSamples* samples = static_cast<AudioSamples*>( value );
650
+ // Transform native data into JS data, passing it to the provided
651
+ // `jsCallback` -- the TSFN's JavaScript function.
652
+ //Napi::Buffer<float> buffer = Napi::Buffer<float>::New(env, samples->OriginalData, samples->Len);
653
+ //Napi::ArrayBuffer buffer32 = Napi::ArrayBuffer::New(env, samples->OriginalData, samples->Len*sizeof(float));
654
+ //Napi::ArrayBuffer buffer16 = Napi::ArrayBuffer::New(env, samples->Data, samples->Len*sizeof(short));
655
+ Napi::Object obj = Napi::Object::New(env);
656
+ obj.Set("peerId", samples->PeerId);
657
+ obj.Set("mediaId", samples->MediaId);
658
+ obj.Set("samples16", Napi::Buffer<short>::New(env, samples->Data, samples->Len));
659
+ obj.Set("samples32", Napi::Buffer<float>::New(env, samples->OriginalData, samples->Len));
660
+
661
+ jsCallback.Call( {obj} );
662
+
663
+ // We're finished with the data.
664
+ delete samples;
665
+ };
666
+
667
+ while (this->_started)
668
+ {
669
+ for (auto it = _mediaStreams.begin(); it != _mediaStreams.end(); it++)
670
+ {
671
+ OdinReturnCode rc = odin_audio_read_data((OdinMediaStreamHandle)it->second.Handle, this->_audioSamplesBuffer, 960);
672
+
673
+ if (odin_is_error(rc)) {
674
+ printf("Odin NodeJS Addon: Failed to read audio data\n");
675
+ break;
676
+ }
677
+
678
+ AudioSamples* samples = new AudioSamples();
679
+ samples->SetSamples(this->_audioSamplesBuffer, 960);
680
+ samples->PeerId = it->second.PeerId;
681
+ samples->MediaId = it->second.Id;
682
+
683
+ // Perform a blocking call
684
+ napi_status status = _audioDataReceivedEventListener.BlockingCall( samples, callback );
685
+ if ( status != napi_ok )
686
+ {
687
+ // Handle error
688
+ printf("Odin NodeJS Addon: Failed to call audio data received callback\n");
689
+ break;
690
+ }
691
+ }
692
+
693
+ std::this_thread::sleep_for( std::chrono::milliseconds ( 20 ) );
694
+ }
695
+
696
+ #ifdef DEBUG
697
+ printf("Odin NodeJS Addon: Handle Audio Thread finished.");
698
+ #endif
699
+
700
+ // Release the thread-safe function
701
+ _audioDataReceivedEventListener.Release();
702
+ } );
703
+ }
704
+
705
+ Napi::Value OdinRoom::CreateAudioStream(const Napi::CallbackInfo &info)
706
+ {
707
+ Napi::Env env = info.Env();
708
+
709
+ // Checking for input of sample rate (int) and number of channels (int)
710
+ if (info.Length() != 2 || !info[0].IsNumber() || !info[1].IsNumber()) {
711
+ Napi::TypeError::New(env, "Sample rate and number of channels expected").ThrowAsJavaScriptException();
712
+ }
713
+
714
+ if (info.Length() == 3 && !info[2].IsObject()) {
715
+ Napi::TypeError::New(env, "Options need to be an object").ThrowAsJavaScriptException();
716
+ }
717
+
718
+ if (info.Length() == 2) {
719
+ return OdinMedia::NewInstance({this->Value(), info[0], info[1]});
720
+ } else {
721
+ return OdinMedia::NewInstance({this->Value(), info[0], info[1], info[2]});
722
+ }
723
+ }
724
+
725
+
726
+ /**
727
+ * Removes an event listener for this room. Requires the event type to remove the listener for.
728
+ * @param info
729
+ * @return
730
+ */
731
+ Napi::Value OdinRoom::GetOwnPeerId(const Napi::CallbackInfo &info) {
732
+ Napi::Env env = info.Env();
733
+
734
+ uint64_t out_peer_id = 0;
735
+ odin_room_peer_id(this->_roomHandle, &out_peer_id);
736
+
737
+ return Napi::Number::New(env, out_peer_id);
738
+ }