@4players/odin-nodejs 0.11.1 → 0.11.3

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/Dockerfile ADDED
@@ -0,0 +1,69 @@
1
+ FROM node:20-bullseye
2
+
3
+ WORKDIR /usr/src/app
4
+
5
+ ARG ODIN_NODEJS_VERSION=latest
6
+
7
+ RUN npm init -y \
8
+ && npm install --omit=dev "@4players/odin-nodejs@${ODIN_NODEJS_VERSION}"
9
+
10
+ RUN cat <<'EOF' > connection-test.js
11
+ const odin = require('@4players/odin-nodejs');
12
+ const { OdinClient } = odin;
13
+
14
+ const accessKey = process.env.ODIN_ACCESS_KEY;
15
+ const roomId = process.env.ODIN_ROOM_ID || 'odin-sdk-ci-test';
16
+ const userId = process.env.ODIN_USER_ID || `docker-example-${Math.floor(Math.random() * 1e6)}`;
17
+ const gateway = process.env.ODIN_GATEWAY || 'https://gateway.odin.4players.io';
18
+ const runDurationMs = Number(process.env.RUN_DURATION_MS || 15000);
19
+
20
+ if (!accessKey) {
21
+ console.error('❌ Missing ODIN_ACCESS_KEY environment variable');
22
+ process.exit(1);
23
+ }
24
+
25
+ const client = new OdinClient();
26
+ const token = client.generateToken(accessKey, roomId, userId);
27
+ const room = client.createRoom(token);
28
+
29
+ room.onConnectionStateChanged((event) => {
30
+ const message = event.message ? ` - ${event.message}` : '';
31
+ console.log(`[state] ${event.state}${message}`);
32
+ });
33
+
34
+ room.onJoined((event) => {
35
+ console.log(`[joined] room=${event.roomId} peer=${event.ownPeerId}`);
36
+ });
37
+
38
+ room.onLeft((event) => {
39
+ console.log(`[left] reason=${event.reason}`);
40
+ });
41
+
42
+ room.onPeerJoined((event) => {
43
+ console.log(`[peer-joined] peer=${event.peerId}`);
44
+ });
45
+
46
+ room.onPeerLeft((event) => {
47
+ console.log(`[peer-left] peer=${event.peerId}`);
48
+ });
49
+
50
+ console.log(`Connecting to ${gateway} as ${userId} (room: ${roomId}) ...`);
51
+ room.join(gateway);
52
+
53
+ setTimeout(() => {
54
+ console.log('✅ Connection test complete, closing room');
55
+ room.close();
56
+ }, runDurationMs);
57
+
58
+ process.on('SIGINT', () => {
59
+ console.log('Received SIGINT, closing room');
60
+ room.close();
61
+ process.exit(0);
62
+ });
63
+ EOF
64
+
65
+ ENV ODIN_GATEWAY=https://gateway.odin.4players.io
66
+ ENV ODIN_ROOM_ID=odin-sdk-ci-test
67
+ ENV RUN_DURATION_MS=15000
68
+
69
+ CMD ["node", "connection-test.js"]
package/README.md CHANGED
@@ -94,6 +94,37 @@ main();
94
94
 
95
95
  ---
96
96
 
97
+ ## Docker Usage
98
+
99
+ The repository includes a ready-to-use `Dockerfile` that shows how to run the SDK inside a Linux/amd64 container. It installs `@4players/odin-nodejs`, is configured for the official ODIN gateway, and runs a lightweight connection test. No manual library-path tweaks are required—the Linux prebuild now embeds `$ORIGIN` so the bundled `libodin*.so` files load automatically.
100
+
101
+ Build the example image:
102
+
103
+ ```bash
104
+ docker build --platform=linux/amd64 -t odin-nodejs-docker-example .
105
+ ```
106
+
107
+ Run it by passing your access key (and optionally a room ID, user ID, or custom gateway):
108
+
109
+ ```bash
110
+ docker run --rm --platform=linux/amd64 \
111
+ -e ODIN_ACCESS_KEY="ATPClAXgmBgY1ryDk/kTC2Yhitf4fJSx95jpN3F9Xac3" \
112
+ -e ODIN_ROOM_ID="my-room" \
113
+ odin-nodejs-docker-example
114
+ ```
115
+
116
+ Environment variables supported by the example:
117
+
118
+ - `ODIN_ACCESS_KEY` *(required)* – access key used to mint tokens.
119
+ - `ODIN_ROOM_ID` *(optional)* – defaults to `odin-sdk-ci-test`.
120
+ - `ODIN_USER_ID` *(optional)* – auto-generates a random ID when omitted.
121
+ - `ODIN_GATEWAY` *(optional)* – defaults to `https://gateway.odin.4players.io`.
122
+ - `RUN_DURATION_MS` *(optional)* – how long to keep the connection open.
123
+
124
+ You can also use the Dockerfile as a starting point for your own services—replace the provided sample script with your application logic.
125
+
126
+ ---
127
+
97
128
  ## Event Handlers
98
129
 
99
130
  The SDK provides typed event handlers for easy integration:
package/binding.gyp CHANGED
@@ -21,10 +21,14 @@
21
21
  "conditions": [
22
22
  ["OS=='linux'", {
23
23
  "libraries": [
24
- "-Wl,-rpath,$$ORIGIN",
25
24
  "-L<(module_root_dir)/libs/bin/linux/<(target_arch)",
26
25
  "-lodin",
27
26
  "-lodin_crypto"
27
+ ],
28
+ "ldflags": [
29
+ "-Wl,-rpath,'$$ORIGIN'",
30
+ "-static-libstdc++",
31
+ "-static-libgcc"
28
32
  ]
29
33
  }],
30
34
  ["OS=='mac'", {
@@ -588,7 +588,7 @@ void OdinRoomWrapper::HandleAudioData() {
588
588
  OdinDecoder* decoder = pair.second;
589
589
  bool silent = false;
590
590
  OdinError rc = odin_decoder_pop(decoder, _audioSamplesBuffer, 1920, &silent);
591
- if (!odin_is_error(rc) && !silent) {
591
+ if (rc == ODIN_ERROR_SUCCESS && !silent) {
592
592
  // Double-check _started before calling callback
593
593
  // This prevents calling a released ThreadSafeFunction
594
594
  if (_started && _audioDataReceivedEventListener) {
@@ -621,8 +621,10 @@ void OdinRoomWrapper::HandleAudioData() {
621
621
  Napi::Object obj = Napi::Object::New(env);
622
622
  obj.Set("mediaId", samples->MediaId);
623
623
  obj.Set("peerId", (double)samples->PeerId);
624
- obj.Set("samples16", Napi::Buffer<short>::New(env, samples->Data, samples->Len));
625
- obj.Set("samples32", Napi::Buffer<float>::New(env, samples->OriginalData, samples->Len));
624
+ // Must use Copy(), not New(). New() wraps the pointer without copying.
625
+ // Since we delete samples below, New() would leave JS holding a dangling pointer.
626
+ obj.Set("samples16", Napi::Buffer<short>::Copy(env, samples->Data, samples->Len));
627
+ obj.Set("samples32", Napi::Buffer<float>::Copy(env, samples->OriginalData, samples->Len));
626
628
  jsCallback.Call({obj});
627
629
  } catch (...) {
628
630
  // Exception in audio callback - silently ignore
@@ -642,7 +644,11 @@ void OdinRoomWrapper::HandleAudioData() {
642
644
  }
643
645
  }
644
646
  }
645
- std::this_thread::sleep_for(std::chrono::milliseconds(10));
647
+ // Sleep must match the decoder frame size (1920 stereo samples @ 48kHz = 20ms).
648
+ // A shorter sleep (e.g. 10ms) polls faster than audio arrives, causing the
649
+ // decoder's jitter buffer to produce PLC (Packet Loss Concealment) frames that
650
+ // stretch and distort the audio stream.
651
+ std::this_thread::sleep_for(std::chrono::milliseconds(20));
646
652
  }
647
653
  }
648
654
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@4players/odin-nodejs",
3
- "version": "0.11.1",
3
+ "version": "0.11.3",
4
4
  "description": "NodeJS bindings for the ODIN SDK. Use for AI enhanced human interactions, content moderation and audio processing features in a backend.",
5
5
  "main": "index.cjs",
6
6
  "types": "index.d.ts",
Binary file
Binary file
@@ -198,8 +198,8 @@ async function run() {
198
198
  // Write audio samples to the recording
199
199
  const recorder = fileRecorder[mediaId];
200
200
  if (recorder && data.samples16) {
201
- // Convert Int16Array to Buffer and write to WAV file
202
- const buffer = Buffer.from(data.samples16.buffer, data.samples16.byteOffset, data.samples16.byteLength);
201
+ // Copy audio data into a new buffer for safe async WAV writing
202
+ const buffer = Buffer.from(data.samples16);
203
203
  recorder.wavEncoder.write(buffer);
204
204
  recorder.sampleCount += data.samples16.length;
205
205
  }