@4players/odin-nodejs 0.10.3 → 0.11.1

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 (73) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/LICENSE +21 -0
  3. package/README.md +603 -44
  4. package/binding.gyp +29 -13
  5. package/cppsrc/binding.cpp +3 -6
  6. package/cppsrc/odinbindings.cpp +9 -45
  7. package/cppsrc/odincipher.cpp +92 -0
  8. package/cppsrc/odincipher.h +32 -0
  9. package/cppsrc/odinclient.cpp +19 -158
  10. package/cppsrc/odinclient.h +2 -5
  11. package/cppsrc/odinmedia.cpp +144 -186
  12. package/cppsrc/odinmedia.h +51 -18
  13. package/cppsrc/odinroom.cpp +675 -635
  14. package/cppsrc/odinroom.h +76 -26
  15. package/cppsrc/utilities.cpp +11 -81
  16. package/cppsrc/utilities.h +25 -140
  17. package/index.cjs +829 -0
  18. package/index.d.ts +3 -4
  19. package/libs/bin/linux/arm64/libodin.so +0 -0
  20. package/libs/bin/linux/arm64/libodin_crypto.so +0 -0
  21. package/libs/bin/linux/ia32/libodin.so +0 -0
  22. package/libs/bin/linux/ia32/libodin_crypto.so +0 -0
  23. package/libs/bin/linux/x64/libodin.so +0 -0
  24. package/libs/bin/linux/x64/libodin_crypto.so +0 -0
  25. package/{prebuilds/darwin-x64/node.napi.node → libs/bin/macos/universal/libodin.dylib} +0 -0
  26. package/libs/bin/macos/universal/libodin_crypto.dylib +0 -0
  27. package/libs/bin/windows/arm64/odin.dll +0 -0
  28. package/libs/bin/windows/arm64/odin.lib +0 -0
  29. package/libs/bin/windows/arm64/odin_crypto.dll +0 -0
  30. package/libs/bin/windows/arm64/odin_crypto.lib +0 -0
  31. package/libs/bin/windows/ia32/odin.dll +0 -0
  32. package/libs/bin/windows/ia32/odin.lib +0 -0
  33. package/libs/bin/windows/ia32/odin_crypto.dll +0 -0
  34. package/libs/bin/windows/ia32/odin_crypto.lib +0 -0
  35. package/libs/bin/windows/x64/odin.dll +0 -0
  36. package/libs/bin/windows/x64/odin.lib +0 -0
  37. package/libs/bin/windows/x64/odin_crypto.dll +0 -0
  38. package/libs/bin/windows/x64/odin_crypto.lib +0 -0
  39. package/libs/include/odin.h +665 -567
  40. package/libs/include/odin_crypto.h +46 -0
  41. package/odin.cipher.d.ts +31 -0
  42. package/odin.media.d.ts +69 -19
  43. package/odin.room.d.ts +348 -7
  44. package/package.json +5 -4
  45. package/prebuilds/{darwin-arm64/node.napi.node → darwin-x64+arm64/libodin.dylib} +0 -0
  46. package/prebuilds/darwin-x64+arm64/libodin_crypto.dylib +0 -0
  47. package/prebuilds/darwin-x64+arm64/node.napi.node +0 -0
  48. package/prebuilds/linux-x64/libodin.so +0 -0
  49. package/prebuilds/linux-x64/libodin_crypto.so +0 -0
  50. package/prebuilds/linux-x64/node.napi.node +0 -0
  51. package/prebuilds/win32-x64/node.napi.node +0 -0
  52. package/prebuilds/win32-x64/odin.dll +0 -0
  53. package/prebuilds/win32-x64/odin_crypto.dll +0 -0
  54. package/scripts/postbuild.cjs +133 -0
  55. package/tests/audio-recording/README.md +97 -12
  56. package/tests/audio-recording/index.js +238 -130
  57. package/tests/connection-test/README.md +97 -0
  58. package/tests/connection-test/index.js +273 -0
  59. package/tests/lifecycle/test-room-cycle.js +169 -0
  60. package/tests/sending-audio/README.md +178 -9
  61. package/tests/sending-audio/canBounce.mp3 +0 -0
  62. package/tests/sending-audio/index.js +250 -87
  63. package/tests/sending-audio/test-kiss-api.js +149 -0
  64. package/tests/sending-audio/test-loop-audio.js +142 -0
  65. package/CMakeLists.txt +0 -25
  66. package/libs/bin/linux/arm64/libodin_static.a +0 -0
  67. package/libs/bin/linux/ia32/libodin_static.a +0 -0
  68. package/libs/bin/linux/x64/libodin_static.a +0 -0
  69. package/libs/bin/macos/arm64/libodin_static.a +0 -0
  70. package/libs/bin/macos/x64/libodin_static.a +0 -0
  71. package/libs/bin/windows/arm64/odin_static.lib +0 -0
  72. package/libs/bin/windows/ia32/odin_static.lib +0 -0
  73. package/libs/bin/windows/x64/odin_static.lib +0 -0
@@ -1,246 +1,204 @@
1
- //
2
- // Created by Phillip Schuster on 11.02.23.
3
- //
4
-
5
1
  #include "odinmedia.h"
6
- #include <string>
2
+ #include "odinroom.h"
3
+ #include "utilities.h"
7
4
  #include <iostream>
8
- #include <codecvt>
9
- #include <locale>
10
5
 
11
6
  using namespace std;
12
7
 
13
- // Required, otherwise an unknown symbol error comes up
14
- Napi::FunctionReference *OdinMedia::constructor;
8
+ Napi::FunctionReference *OdinMediaWrapper::constructor;
15
9
 
16
10
  /**
17
- * Returns the media id as string of this room instance
18
- * @param info
11
+ * Constructs an OdinMediaWrapper for encoding and transmitting audio.
12
+ * This maps to odin_encoder_create() in the core SDK.
13
+ *
14
+ * @param info[0] OdinRoom instance - parent room for transmission
15
+ * @param info[1] Sample rate (number) - typically 48000
16
+ * @param info[2] Number of channels (number) - 1 for mono, 2 for stereo
19
17
  */
20
- Napi::Value OdinMedia::MediaId(const Napi::CallbackInfo &info) {
18
+ OdinMediaWrapper::OdinMediaWrapper(const Napi::CallbackInfo &info) : Napi::ObjectWrap<OdinMediaWrapper>(info) {
21
19
  Napi::Env env = info.Env();
22
- Napi::HandleScope scope(env);
23
-
24
- uint16_t out_id;
25
- OdinReturnCode error = odin_media_stream_media_id(_mediaStreamHandle, &out_id);
26
- if (odin_is_error(error)) {
27
- Napi::TypeError::New(env, "Failed to get media id").ThrowAsJavaScriptException();
28
- return env.Undefined();
29
- }
30
-
31
- return Napi::Number::New(env, out_id);
32
- }
33
20
 
34
- /**
35
- * Creates a new OdinMedia instance. Requires an Odin token as a parameter.
36
- * @param info Napi::CallbackInfo
37
- */
38
- OdinMedia::OdinMedia(const Napi::CallbackInfo &info) : Napi::ObjectWrap<OdinMedia>(info) {
39
- Napi::Env env = info.Env();
40
- Napi::HandleScope scope(env);
41
-
42
- // Checking for room instance, sample rate (float) and channels (int)
43
21
  if (info.Length() < 3 || !info[0].IsObject() || !info[1].IsNumber() || !info[2].IsNumber()) {
44
- Napi::TypeError::New(env, "Provide an OdinRoom instance, sample rate and number of channels as number").ThrowAsJavaScriptException();
45
- return;
46
- }
47
-
48
- if (info.Length() == 4 && !info[3].IsObject()) {
49
- Napi::TypeError::New(env, "Options need to be provided as an object").ThrowAsJavaScriptException();
22
+ Napi::TypeError::New(env, "Provide an OdinRoom instance, sample rate and number of channels").ThrowAsJavaScriptException();
50
23
  return;
51
24
  }
52
25
 
53
- _room = Napi::ObjectWrap<OdinRoom>::Unwrap(info[0].As<Napi::Object>());
26
+ _roomWrapper = Napi::ObjectWrap<OdinRoomWrapper>::Unwrap(info[0].As<Napi::Object>());
54
27
  _sampleRate = info[1].As<Napi::Number>().Uint32Value();
55
28
  _numChannels = info[2].As<Napi::Number>().Int32Value();
56
-
57
- /*
58
- * Configure audio processing options for the room
59
- */
60
- OdinApmConfig apm_config;
61
- apm_config.voice_activity_detection = false;
62
- apm_config.voice_activity_detection_attack_probability = 0.9;
63
- apm_config.voice_activity_detection_release_probability = 0.8;
64
- apm_config.volume_gate = false;
65
- apm_config.volume_gate_attack_loudness = -30;
66
- apm_config.volume_gate_release_loudness = -40;
67
- apm_config.echo_canceller = false;
68
- apm_config.high_pass_filter = false;
69
- apm_config.pre_amplifier = false;
70
- apm_config.noise_suppression_level = OdinNoiseSuppressionLevel_Moderate;
71
- apm_config.transient_suppressor = false;
72
- apm_config.gain_controller = true;
73
-
74
- if (info.Length() == 4) {
75
- Napi::Object options = info[3].As<Napi::Object>();
76
- if (options.Has("voiceActivityDetection")) {
77
- apm_config.voice_activity_detection = options.Get("voiceActivityDetection").As<Napi::Boolean>().Value();
78
- }
79
- if (options.Has("voiceActivityDetectionAttackProbability")) {
80
- apm_config.voice_activity_detection_attack_probability = options.Get("voiceActivityDetectionAttackProbability").As<Napi::Number>().DoubleValue();
81
- }
82
- if (options.Has("voiceActivityDetectionReleaseProbability")) {
83
- apm_config.voice_activity_detection_release_probability = options.Get("voiceActivityDetectionReleaseProbability").As<Napi::Number>().DoubleValue();
84
- }
85
- if (options.Has("volumeGate")) {
86
- apm_config.volume_gate = options.Get("volumeGate").As<Napi::Boolean>().Value();
87
- }
88
- if (options.Has("volumeGateAttackLoudness")) {
89
- apm_config.volume_gate_attack_loudness = options.Get("volumeGateAttackLoudness").As<Napi::Number>().DoubleValue();
90
- }
91
- if (options.Has("volumeGateReleaseLoudness")) {
92
- apm_config.volume_gate_release_loudness = options.Get("volumeGateReleaseLoudness").As<Napi::Number>().DoubleValue();
93
- }
94
- if (options.Has("echoCanceller")) {
95
- apm_config.echo_canceller = options.Get("echoCanceller").As<Napi::Boolean>().Value();
96
- }
97
- if (options.Has("highPassFilter")) {
98
- apm_config.high_pass_filter = options.Get("highPassFilter").As<Napi::Boolean>().Value();
99
- }
100
- if (options.Has("preAmplifier")) {
101
- apm_config.pre_amplifier = options.Get("preAmplifier").As<Napi::Boolean>().Value();
102
- }
103
- if (options.Has("noiseSuppressionLevel")) {
104
- apm_config.noise_suppression_level = (OdinNoiseSuppressionLevel) options.Get("noiseSuppressionLevel").As<Napi::Number>().Int32Value();
105
- }
106
- if (options.Has("transientSuppressor")) {
107
- apm_config.transient_suppressor = options.Get("transientSuppressor").As<Napi::Boolean>().Value();
108
- }
109
- if (options.Has("gainController")) {
110
- apm_config.gain_controller = options.Get("gainController").As<Napi::Boolean>().Value();
111
- }
112
- }
113
-
114
- OdinReturnCode error = odin_room_configure_apm(_room->GetRoomHandle(), apm_config);
115
- if (odin_is_error(error)) {
116
- OdinUtilities::ThrowNapiException(env, error, "Failed to configure APM settings");
117
- }
118
-
119
- /*
120
- * Create the input audio stream with a samplerate of 48 kHz
121
- */
122
- OdinAudioStreamConfig audio_config;
123
- audio_config.sample_rate = _sampleRate;
124
- audio_config.channel_count = _numChannels;
125
- _mediaStreamHandle = odin_audio_stream_create(audio_config);
126
-
127
- error = odin_room_add_media(_room->GetRoomHandle(), _mediaStreamHandle);
128
- if (odin_is_error(error)) {
129
- OdinUtilities::ThrowNapiException(env, error, "Failed to add media to room");
29
+ _mediaId = 0; // Will be set via setMediaId() after Joined event
30
+ _closed = false;
31
+ _encoder = nullptr;
32
+
33
+ // Create the ODIN encoder with the specified sample rate and stereo mode
34
+ OdinError rc = odin_encoder_create(_sampleRate, _numChannels == 2, &_encoder);
35
+ if (odin_is_error(rc)) {
36
+ OdinUtilities::ThrowNapiException(env, rc, "Failed to create encoder");
130
37
  }
131
38
  }
132
39
 
133
40
  /**
134
- * Destructor. Closes and destroys the OdinMedia and resets the event callback for this room handle.
41
+ * Releases resources when the JavaScript object is garbage collected.
42
+ * Uses mutex to prevent race conditions with Close().
135
43
  */
136
- void OdinMedia::Finalize(Napi::Env env) {
137
- odin_media_stream_destroy(_mediaStreamHandle);
44
+ void OdinMediaWrapper::Finalize(Napi::Env env) {
45
+ std::lock_guard<std::mutex> lock(_closeMutex);
46
+
47
+ _closed = true;
48
+
49
+ if (_encoder) {
50
+ odin_encoder_free(_encoder);
51
+ _encoder = nullptr;
52
+ }
53
+
54
+ _roomWrapper = nullptr;
138
55
  }
139
56
 
140
57
  /**
141
- * Exposes this class to JavaScript
142
- * @param info Napi::CallbackInfo
58
+ * Initializes the OdinMedia class and registers it with the N-API module.
59
+ * Exposes: sendAudioData, setMediaId, close, and id property.
60
+ *
61
+ * Note: start() and stop() were removed as they served no purpose -
62
+ * they set a _running flag that was never checked. This simplifies
63
+ * the API to match the core ODIN SDK pattern.
143
64
  */
144
- Napi::Object OdinMedia::Init(Napi::Env env, Napi::Object exports) {
145
- // This method is used to hook the accessor and method callbacks
65
+ Napi::Object OdinMediaWrapper::Init(Napi::Env env, Napi::Object exports) {
146
66
  Napi::Function func = DefineClass(env, "OdinMedia", {
147
- InstanceMethod<&OdinMedia::Close>("close", static_cast<napi_property_attributes>(napi_writable | napi_configurable)),
148
- InstanceMethod<&OdinMedia::SendData>("sendAudioData", static_cast<napi_property_attributes>(napi_writable | napi_configurable)),
149
- InstanceAccessor("id", &OdinMedia::MediaId, nullptr, static_cast<napi_property_attributes>(napi_writable | napi_configurable)),
150
- StaticMethod<&OdinMedia::CreateNewItem>("CreateNewItem", static_cast<napi_property_attributes>(napi_writable | napi_configurable)),
67
+ InstanceMethod<&OdinMediaWrapper::Close>("close", static_cast<napi_property_attributes>(napi_writable | napi_configurable)),
68
+ InstanceMethod<&OdinMediaWrapper::SendData>("sendAudioData", static_cast<napi_property_attributes>(napi_writable | napi_configurable)),
69
+ InstanceMethod<&OdinMediaWrapper::SetMediaId>("setMediaId", static_cast<napi_property_attributes>(napi_writable | napi_configurable)),
70
+ InstanceAccessor("id", &OdinMediaWrapper::MediaId, nullptr, static_cast<napi_property_attributes>(napi_writable | napi_configurable)),
71
+ StaticMethod<&OdinMediaWrapper::CreateNewItem>("CreateNewItem", static_cast<napi_property_attributes>(napi_writable | napi_configurable)),
151
72
  });
152
73
 
153
- // We use a static variable to store the constructor. This is because we need to create instances within C++ and
154
- // expose to JavaScript. We can't use the constructor directly because it's not exposed to JavaScript.
155
74
  constructor = new Napi::FunctionReference();
156
-
157
- // Create a persistent reference to the class constructor. This will allow
158
- // a function called on a class prototype and a function
159
- // called on instance of a class to be distinguished from each other.
160
75
  *constructor = Napi::Persistent(func);
161
76
  exports.Set("OdinMedia", func);
162
77
 
163
- // Store the constructor as the add-on instance data. This will allow this
164
- // add-on to support multiple instances of itself running on multiple worker
165
- // threads, as well as multiple instances of itself running in different
166
- // contexts on the same thread.
167
- //
168
- // By default, the value set on the environment here will be destroyed when
169
- // the add-on is unloaded using the `delete` operator, but it is also
170
- // possible to supply a custom deleter.
171
- env.SetInstanceData<Napi::FunctionReference>(constructor);
172
-
173
78
  return exports;
174
79
  }
175
80
 
176
81
  /**
177
- * Closes the room connection and destroys the room handle.
178
- * @param info
82
+ * Closes the media stream and releases resources.
83
+ * This maps to odin_encoder_free() in the core SDK.
84
+ *
85
+ * Thread Safety:
86
+ * Sets _closed to true FIRST (atomically), then acquires the mutex to free resources.
87
+ * This ensures that other methods see _closed=true immediately and exit early.
179
88
  */
180
- void OdinMedia::Close(const Napi::CallbackInfo &info) {
181
- //Napi::Env env = info.Env();
89
+ void OdinMediaWrapper::Close(const Napi::CallbackInfo &info) {
90
+ // Set closed flag first - this is atomic and immediately visible to other methods.
91
+ bool wasClosed = _closed.exchange(true);
92
+ if (wasClosed) {
93
+ return; // Already closed
94
+ }
95
+
96
+ // Acquire mutex to safely free resources
97
+ std::lock_guard<std::mutex> lock(_closeMutex);
98
+
99
+ if (_encoder) {
100
+ odin_encoder_free(_encoder);
101
+ _encoder = nullptr;
102
+ }
103
+
104
+ _roomWrapper = nullptr;
105
+ }
182
106
 
183
- if (_mediaStreamHandle <= 0) return;
107
+ Napi::Value OdinMediaWrapper::CreateNewItem(const Napi::CallbackInfo &info) {
108
+ return constructor->New({info[0]});
109
+ }
184
110
 
185
- odin_media_stream_destroy(_mediaStreamHandle);
186
- _mediaStreamHandle = 0;
111
+ Napi::Object OdinMediaWrapper::NewInstance(const std::vector<napi_value>& args) {
112
+ return constructor->New(args);
187
113
  }
188
114
 
189
115
  /**
190
- * Creates a new instance of the OdinMedia class. Requires a token as a parameter.
191
- * @param info
192
- * @return
116
+ * Sets the server-assigned media ID.
117
+ * This must be called after receiving the Joined event which provides
118
+ * the list of available media IDs for this peer.
119
+ *
120
+ * @param info[0] Media ID (number) - from Joined event's mediaIds array
193
121
  */
194
- Napi::Value OdinMedia::CreateNewItem(const Napi::CallbackInfo &info) {
122
+ void OdinMediaWrapper::SetMediaId(const Napi::CallbackInfo &info) {
123
+ if (_closed) return;
124
+
195
125
  Napi::Env env = info.Env();
196
-
197
- if (info.Length() < 1 || !info[0].IsObject()) {
198
- Napi::TypeError::New(env, "Token required as first parameter").ThrowAsJavaScriptException();
126
+
127
+ if (info.Length() < 1 || !info[0].IsNumber()) {
128
+ Napi::TypeError::New(env, "Media ID (number) required").ThrowAsJavaScriptException();
129
+ return;
199
130
  }
200
-
201
- #ifdef DEBUG
202
- printf("Odin NodeJS Addon: Creating OdinMedia Instance\n");
203
- #endif
204
-
205
- // Retrieve the instance data we stored during `Init()`. We only stored the
206
- // constructor there, so we retrieve it here to create a new instance of the
207
- // JS class the constructor represents.
208
- Napi::FunctionReference *constructor =
209
- info.Env().GetInstanceData<Napi::FunctionReference>();
210
- return constructor->New({info[0]});
131
+
132
+ _mediaId = static_cast<uint16_t>(info[0].As<Napi::Number>().Uint32Value());
211
133
  }
212
134
 
213
135
  /**
214
- * Creates a new instance of the OdinMedia class. Requires a token as a parameter.
215
- * @param info
216
- * @return
136
+ * Sends audio samples for transmission.
137
+ * This maps to odin_encoder_push() + odin_encoder_pop() + odin_room_send_datagram() in the core SDK.
138
+ *
139
+ * Pushes samples to the encoder, then immediately pops and sends all available datagrams.
140
+ * If mediaId is 0 or the stream is closed, this is a no-op (matches core SDK behavior).
141
+ *
142
+ * @param info[0] Float32Array of interleaved audio samples in range [-1, 1]
217
143
  */
218
- Napi::Object OdinMedia::NewInstance(const std::initializer_list<napi_value>& args) {
219
- //printf("Creating OdinMedia Instance, sample rate %d, num channels %d\n", sampleRate.As<Napi::Number>().Uint32Value(), numChannels.As<Napi::Number>().Uint32Value());
220
-
221
- #ifdef DEBUG
222
- printf("Odin NodeJS Addon: Creating OdinMedia Instance with %d arguments\n", (int)args.size());
223
- #endif
224
-
225
- return constructor->New(args);
226
- }
227
-
228
- void OdinMedia::SendData(const Napi::CallbackInfo &info) {
144
+ void OdinMediaWrapper::SendData(const Napi::CallbackInfo &info) {
145
+ // Early exit if closed - prevents use-after-free
146
+ if (_closed) return;
147
+
229
148
  Napi::Env env = info.Env();
230
149
 
231
150
  if (info.Length() < 1 || !info[0].IsTypedArray()) {
232
- Napi::TypeError::New(env, "TypedArray required as first parameter").ThrowAsJavaScriptException();
151
+ Napi::TypeError::New(env, "Float32Array required as first parameter").ThrowAsJavaScriptException();
152
+ return;
153
+ }
154
+
155
+ // Check resources are valid
156
+ if (!_encoder || !_roomWrapper || !_roomWrapper->GetRoom()) {
157
+ return;
158
+ }
159
+
160
+ // mediaId must be set before sending (will produce no output if 0)
161
+ if (_mediaId == 0) {
233
162
  return;
234
163
  }
235
164
 
236
165
  Napi::Float32Array array = info[0].As<Napi::Float32Array>();
166
+ const float* samples = array.Data();
167
+ uint32_t sampleCount = array.ElementLength();
237
168
 
238
- #ifdef DEBUG
239
- printf("Sent %d bytes of audio data\n", (int)array.ElementLength());
240
- #endif
169
+ // Push samples to encoder
170
+ OdinError err = odin_encoder_push(_encoder, samples, sampleCount);
171
+ if (odin_is_error(err)) {
172
+ std::cerr << "odin_encoder_push failed: " << odin_error_get_last_error() << std::endl;
173
+ return;
174
+ }
241
175
 
242
- OdinReturnCode result = odin_audio_push_data(_mediaStreamHandle, array.Data(), array.ElementLength());
243
- if (odin_is_error(result)) {
244
- OdinUtilities::ThrowNapiException(env, result, "Failed to push audio data");
176
+ // Pop and send all available datagrams
177
+ uint8_t datagram[2048];
178
+ for (;;) {
179
+ if (_closed) return; // Check again in loop
180
+
181
+ uint32_t datagram_length = sizeof(datagram);
182
+
183
+ switch (odin_encoder_pop(_encoder, &_mediaId, 1, datagram, &datagram_length)) {
184
+ case ODIN_ERROR_SUCCESS:
185
+ if (_roomWrapper && _roomWrapper->GetRoom()) {
186
+ odin_room_send_datagram(_roomWrapper->GetRoom(), datagram, datagram_length);
187
+ }
188
+ break;
189
+ case ODIN_ERROR_NO_DATA:
190
+ return;
191
+ default:
192
+ std::cerr << "odin_encoder_pop failed: " << odin_error_get_last_error() << std::endl;
193
+ return;
194
+ }
245
195
  }
196
+ }
197
+
198
+ /**
199
+ * Returns the media ID for this stream.
200
+ * @returns The server-assigned media ID, or 0 if not yet set.
201
+ */
202
+ Napi::Value OdinMediaWrapper::MediaId(const Napi::CallbackInfo &info) {
203
+ return Napi::Number::New(info.Env(), _mediaId);
246
204
  }
@@ -1,35 +1,68 @@
1
- //
2
- // Created by Phillip Schuster on 11.02.23.
3
- //
4
-
5
1
  #ifndef ODIN_NODEJS_ODINMEDIA_H
6
2
  #define ODIN_NODEJS_ODINMEDIA_H
7
3
 
8
4
  #include <napi.h>
9
5
  #include <odin.h>
10
- #include "utilities.h"
11
- #include <map>
12
- #include <thread>
13
- #include "odinroom.h"
6
+ #include <vector>
7
+ #include <atomic>
8
+ #include <mutex>
9
+
10
+ class OdinRoomWrapper;
14
11
 
15
- class OdinMedia : public Napi::ObjectWrap<OdinMedia> {
12
+ /**
13
+ * OdinMediaWrapper handles encoding and transmitting audio data to the ODIN server.
14
+ *
15
+ * This wrapper closely follows the core ODIN SDK pattern:
16
+ * 1. Constructor creates encoder (odin_encoder_create)
17
+ * 2. setMediaId() sets the server-assigned media ID
18
+ * 3. sendAudioData() encodes samples and sends datagrams (odin_encoder_push/pop + odin_room_send_datagram)
19
+ * 4. close() frees the encoder (odin_encoder_free)
20
+ *
21
+ * Usage workflow (from JavaScript):
22
+ * 1. Wait for Joined event to receive available media_ids
23
+ * 2. Create audio stream: media = room.createAudioStream(48000, 2)
24
+ * 3. Set the server-assigned media ID: media.setMediaId(mediaIds[0])
25
+ * 4. Push audio chunks: media.sendAudioData(chunk) // 20ms chunks recommended
26
+ * 5. When done: media.close()
27
+ *
28
+ * Thread Safety:
29
+ * The close() method sets _closed atomically before freeing resources.
30
+ * All methods check _closed first to prevent use-after-free crashes.
31
+ */
32
+ class OdinMediaWrapper : public Napi::ObjectWrap<OdinMediaWrapper> {
16
33
  public:
17
- OdinMedia(const Napi::CallbackInfo& info); //Constructor to initialise
18
- static Napi::FunctionReference *New;
34
+ OdinMediaWrapper(const Napi::CallbackInfo& info);
19
35
  static Napi::Object Init(Napi::Env env, Napi::Object exports);
20
36
  static Napi::Value CreateNewItem(const Napi::CallbackInfo& info);
37
+ static Napi::Object NewInstance(const std::vector<napi_value>& args);
21
38
  void Finalize(Napi::Env env) override;
22
- static Napi::FunctionReference *constructor;
23
- static Napi::Object NewInstance(const std::initializer_list<napi_value>& args);
39
+
24
40
  private:
25
- OdinMediaStreamHandle _mediaStreamHandle;
26
- OdinRoom* _room;
41
+ static Napi::FunctionReference *constructor;
42
+
43
+ // Room and encoder references
44
+ OdinRoomWrapper* _roomWrapper;
45
+ struct OdinEncoder* _encoder;
27
46
  uint32_t _sampleRate;
28
- uint8_t _numChannels;
47
+ int _numChannels;
48
+
49
+ // Server-assigned media ID (received from Joined event)
50
+ uint16_t _mediaId;
51
+
52
+ // Closed state - once set, all operations are rejected to prevent use-after-free.
53
+ // This is set BEFORE freeing resources in Close() to ensure thread safety.
54
+ std::atomic<bool> _closed;
55
+
56
+ // Mutex for protecting resource cleanup
57
+ std::mutex _closeMutex;
58
+
59
+ // Native methods exposed to JavaScript
29
60
  void Close(const Napi::CallbackInfo& info);
30
61
  void SendData(const Napi::CallbackInfo& info);
31
- Napi::Value MediaId(const Napi::CallbackInfo &info);
62
+ void SetMediaId(const Napi::CallbackInfo& info);
63
+ Napi::Value MediaId(const Napi::CallbackInfo& info);
32
64
  };
33
65
 
34
-
35
66
  #endif //ODIN_NODEJS_ODINMEDIA_H
67
+
68
+