@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 +69 -0
- package/README.md +31 -0
- package/binding.gyp +5 -1
- package/cppsrc/odinroom.cpp +10 -4
- package/package.json +1 -1
- package/prebuilds/darwin-x64+arm64/node.napi.node +0 -0
- package/prebuilds/linux-x64/node.napi.node +0 -0
- package/prebuilds/win32-x64/node.napi.node +0 -0
- package/tests/audio-recording/index.js +2 -2
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'", {
|
package/cppsrc/odinroom.cpp
CHANGED
|
@@ -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 (
|
|
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
|
-
|
|
625
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
//
|
|
202
|
-
const buffer = Buffer.from(data.samples16
|
|
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
|
}
|