
Product
Socket for Jira Is Now Available
Socket for Jira lets teams turn alerts into Jira tickets with manual creation, automated ticketing rules, and two-way sync.
@4players/odin-nodejs
Advanced tools
NodeJS bindings for the ODIN SDK. Use for AI enhanced human interactions, content moderation and audio processing features in a backend.
Native Node.js bindings for the ODIN Voice SDK. Build powerful voice chat applications, recording bots, AI integrations, and real-time audio processing tools.
📖 Full Documentation | 💬 Discord Community | 🎮 4Players ODIN
npm install @4players/odin-nodejs
This SDK includes prebuilt binaries for:
For other platforms, you'll need a C++ compiler. See node-gyp requirements.
Sign up at 4Players ODIN to get your free access key.
import odin from '@4players/odin-nodejs';
const { OdinClient } = odin;
// Configuration - replace with your credentials
const accessKey = "__YOUR_ACCESS_KEY__";
const roomId = "my-room";
const userId = "user-123";
async function main() {
// Create client and generate token locally
const client = new OdinClient();
const token = client.generateToken(accessKey, roomId, userId);
// Create room using factory pattern
const room = client.createRoom(token);
// Set up event handlers
room.onJoined((event) => {
console.log(`Joined room: ${event.roomId}`);
console.log(`My peer ID: ${event.ownPeerId}`);
console.log(`Available media IDs: ${event.mediaIds}`);
});
room.onPeerJoined((event) => {
console.log(`Peer joined: ${event.peerId}`);
});
room.onPeerLeft((event) => {
console.log(`Peer left: ${event.peerId}`);
});
// Join the room
room.join("https://gateway.odin.4players.io");
// Keep connection alive
process.on('SIGINT', () => {
room.close();
process.exit(0);
});
}
main();
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.
Build the example image:
docker build --platform=linux/amd64 -t odin-nodejs-docker-example .
Run it by passing your access key (and optionally a room ID, user ID, or custom gateway):
docker run --rm --platform=linux/amd64 \
-e ODIN_ACCESS_KEY="ATPClAXgmBgY1ryDk/kTC2Yhitf4fJSx95jpN3F9Xac3" \
-e ODIN_ROOM_ID="my-room" \
odin-nodejs-docker-example
Environment variables supported by the example:
ODIN_ACCESS_KEY (required) – access key used to mint tokens.ODIN_ROOM_ID (optional) – defaults to odin-sdk-ci-test.ODIN_USER_ID (optional) – auto-generates a random ID when omitted.ODIN_GATEWAY (optional) – defaults to https://gateway.odin.4players.io.RUN_DURATION_MS (optional) – how long to keep the connection open.You can also use the Dockerfile as a starting point for your own services—replace the provided sample script with your application logic.
The SDK provides typed event handlers for easy integration:
// Connection events
room.onConnectionStateChanged((event) => {
console.log(`State: ${event.state}`); // Connecting, Joined, Disconnected, etc.
});
room.onJoined((event) => {
// { roomId, ownPeerId, room, mediaIds }
});
room.onLeft((event) => {
// { reason }
});
// Peer events
room.onPeerJoined((event) => {
// { peerId, userId, userData, peer }
});
room.onPeerLeft((event) => {
// { peerId }
});
// Media events
room.onMediaStarted((event) => {
// { peerId, media }
});
room.onMediaStopped((event) => {
// { peerId, mediaId }
});
room.onMediaActivity((event) => {
// { peerId, mediaId, state } - Voice Activity Detection
});
// Messages
room.onMessageReceived((event) => {
// { senderPeerId, message }
});
// Audio data (for recording/processing)
room.onAudioDataReceived((data) => {
// { peerId, mediaId, samples16, samples32 }
});
Record audio from peers to WAV files:
import odin from '@4players/odin-nodejs';
import wav from 'wav';
const { OdinClient } = odin;
// Configuration
const accessKey = "__YOUR_ACCESS_KEY__";
const roomId = "my-room";
const userId = "RecorderBot";
const recordings = {};
async function main() {
const client = new OdinClient();
const token = client.generateToken(accessKey, roomId, userId);
const room = client.createRoom(token);
room.onAudioDataReceived((data) => {
const { mediaId, peerId, samples16 } = data;
// Create recording file if needed
if (!recordings[mediaId]) {
recordings[mediaId] = new wav.FileWriter(`recording_${peerId}.wav`, {
channels: 2,
sampleRate: 48000,
bitDepth: 16
});
}
// Write audio samples
const buffer = Buffer.from(samples16.buffer, samples16.byteOffset, samples16.byteLength);
recordings[mediaId].write(buffer);
});
room.onMediaStopped((event) => {
if (recordings[event.mediaId]) {
recordings[event.mediaId].end();
delete recordings[event.mediaId];
}
});
room.join("https://gateway.odin.4players.io");
}
main();
The SDK provides two approaches for sending audio: a high-level API for convenience and a low-level API for full control.
The high-level API handles all the complexity automatically - media ID allocation, StartMedia RPC, and timing:
import odin from '@4players/odin-nodejs';
const { OdinClient } = odin;
// Configuration
const accessKey = "__YOUR_ACCESS_KEY__";
const roomId = "my-room";
const userId = "AudioBot";
async function main() {
const client = new OdinClient();
const token = client.generateToken(accessKey, roomId, userId);
const room = client.createRoom(token);
// Wait for room join
const joinPromise = new Promise(resolve => room.onJoined(resolve));
room.join("https://gateway.odin.4players.io");
await joinPromise;
// Create audio stream and send audio with one line!
const media = room.createAudioStream(44100, 2);
// Send an MP3 file (auto-decodes and streams with correct timing)
await media.sendMP3('./music.mp3');
// Or send a WAV file
await media.sendWAV('./audio.wav');
// Or send a decoded AudioBuffer
// await media.sendBuffer(audioBuffer);
media.close();
room.close();
}
main();
For full control over audio transmission, use the low-level API:
import odin from '@4players/odin-nodejs';
const { OdinClient } = odin;
import { encode } from '@msgpack/msgpack';
// Configuration
const accessKey = "__YOUR_ACCESS_KEY__";
const roomId = "my-room";
const userId = "AudioBot";
async function main() {
const client = new OdinClient();
const token = client.generateToken(accessKey, roomId, userId);
const room = client.createRoom(token);
room.onJoined(async (event) => {
// 1. Get media ID from the event
const mediaId = event.mediaIds[0];
// 2. Create audio stream
const media = room.createAudioStream(48000, 2);
// 3. Set the server-assigned media ID
media.setMediaId(mediaId);
// 4. Send StartMedia RPC to notify server
const rpc = encode([0, 1, "StartMedia", {
media_id: mediaId,
properties: { kind: "audio" }
}]);
room.sendRpc(new Uint8Array(rpc));
// 5. Send audio data in 20ms chunks
const chunkDurationMs = 20;
const samplesPerChunk = Math.floor(48000 * chunkDurationMs / 1000) * 2;
// Your audio data as Float32Array (interleaved stereo, range [-1, 1])
const audioChunk = new Float32Array(samplesPerChunk);
// ... fill with audio samples ...
media.sendAudioData(audioChunk);
// 6. When done, close
media.close();
});
room.join("https://gateway.odin.4players.io");
}
main();
See tests/sending-audio/ for complete examples of both APIs.
Enable encryption for secure voice communication:
import odin from '@4players/odin-nodejs';
const { OdinClient, OdinCipher } = odin;
const client = new OdinClient();
const token = client.generateToken(accessKey, roomId, userId);
const room = client.createRoom(token);
// Create and configure cipher
const cipher = new OdinCipher();
cipher.setPassword(new TextEncoder().encode("shared-secret-password"));
// Apply cipher to room
room.setCipher(cipher);
room.join("https://gateway.odin.4players.io");
⚠️ All participants in a room must use the same cipher password to communicate.
// Check if a peer's encryption matches ours
const status = cipher.getPeerStatus(peerId);
console.log(`Peer ${peerId} encryption: ${status}`);
// Possible values: "encrypted", "mismatch", "unencrypted", "unknown"
Enable distance-based audio for spatial applications:
room.onJoined(() => {
// Set position scale (1 unit = 1 meter)
room.setPositionScale(1.0);
// Update your position
room.updatePosition(10.0, 0.0, 5.0); // x, y, z
});
Monitor connection quality and troubleshoot issues:
room.onJoined(() => {
// Get connection identifier
const connectionId = room.getConnectionId();
console.log(`Connection ID: ${connectionId}`);
// Get detailed connection statistics
const stats = room.getConnectionStats();
if (stats) {
console.log(`RTT: ${stats.rtt.toFixed(2)} ms`);
console.log(`TX Loss: ${(stats.udpTxLoss * 100).toFixed(2)}%`);
console.log(`RX Loss: ${(stats.udpRxLoss * 100).toFixed(2)}%`);
console.log(`TX Bytes: ${stats.udpTxBytes}`);
console.log(`RX Bytes: ${stats.udpRxBytes}`);
console.log(`Congestion Events: ${stats.congestionEvents}`);
}
// Get jitter statistics for an audio stream
const jitterStats = room.getJitterStats(mediaId);
if (jitterStats) {
console.log(`Packets Total: ${jitterStats.packetsTotal}`);
console.log(`Packets Lost: ${jitterStats.packetsLost}`);
console.log(`Packets Too Late: ${jitterStats.packetsArrivedTooLate}`);
}
});
| Method | Description |
|---|---|
generateToken(accessKey, roomId, userId) | Generate a room token locally |
createRoom(token) | Create a room instance (recommended) |
createRoomWithToken(token) | Alias for createRoom |
| Method | Description |
|---|---|
join(gateway, userData?) | Connect to the room |
close() | Disconnect from the room |
sendMessage(data, peerIds?) | Send a message to peers |
updatePosition(x, y, z) | Update 3D position |
setPositionScale(scale) | Set position scale factor |
setCipher(cipher) | Enable E2EE |
createAudioStream(sampleRate, channels) | Create audio output stream |
getConnectionId() | Get connection identifier |
getConnectionStats() | Get connection quality metrics |
getJitterStats(mediaId) | Get audio jitter metrics |
| Property | Type | Description |
|---|---|---|
ownPeerId | number | Your peer ID |
connected | boolean | Connection status |
availableMediaIds | number[] | Available media IDs for audio streams |
| Method | Description |
|---|---|
setMediaId(mediaId) | Set server-assigned media ID |
close() | Release the stream |
sendAudioData(samples) | Send raw audio samples |
sendMP3(filePath) | Stream an MP3 file (convenience) |
sendWAV(filePath) | Stream a WAV file (convenience) |
sendBuffer(audioBuffer) | Stream AudioBuffer (convenience) |
| Method | Description |
|---|---|
setPassword(password) | Set encryption password |
getPeerStatus(peerId) | Get peer's encryption status |
| Event | Payload |
|---|---|
ConnectionStateChanged | { state, message } |
Joined | { roomId, ownPeerId, room, mediaIds } |
Left | { reason } |
PeerJoined | { peerId, userId, userData, peer } |
PeerLeft | { peerId } |
MediaStarted | { peerId, media } |
MediaStopped | { peerId, mediaId } |
MediaActivity | { peerId, mediaId, state } |
MessageReceived | { senderPeerId, message } |
AudioDataReceived | { peerId, mediaId, samples16, samples32 } |
| Feature | Node.js SDK | Web SDK |
|---|---|---|
| Platform | Node.js (server) | Browser |
| Performance | Native C++ | WebRTC/JavaScript |
| Raw Audio Access | ✅ Full PCM data | ⚠️ Web Audio API |
| Use Cases | Bots, recording, AI | Client apps |
| E2EE | ✅ OdinCipher | ✅ OdinCipher |
The Node.js SDK is optimized for server-side use cases like:
Check the tests/ folder for complete examples:
If you encounter build errors, ensure you have the required tools:
# macOS
xcode-select --install
# Ubuntu/Debian
sudo apt-get install build-essential python3
# Windows
npm install --global windows-build-tools
If you see "code signature not valid" errors:
cd node_modules/@4players/odin-nodejs/build/Debug
xattr -cr *.dylib
codesign -f -s - *.dylib
This package includes prebuilt binaries for common platforms (macOS x64/arm64, Windows x64, Linux x64). If you need to build for a different platform or architecture, follow these steps:
You'll need a C++ compiler toolchain:
# macOS
xcode-select --install
# Ubuntu/Debian
sudo apt-get install build-essential python3
# Windows
npm install --global windows-build-tools
Download the ODIN SDK libraries from the official releases:
| Platform | Architecture | Target Directory |
|---|---|---|
| Linux | x64 | libs/bin/linux/x64/ |
| Linux | arm64 | libs/bin/linux/arm64/ |
| Linux | ia32 | libs/bin/linux/ia32/ |
| macOS | Universal | libs/bin/macos/universal/ |
| Windows | x64 | libs/bin/windows/x64/ |
| Windows | ia32 | libs/bin/windows/ia32/ |
The SDK archive contains these library files:
libodin_static.a, libodin.so, libodin_crypto_static.a, libodin_crypto.solibodin.dylib, libodin_crypto.dylib, libodin_static.a, libodin_crypto_static.aodin_static.lib, odin.dll, odin_crypto_static.lib, odin_crypto.dll# Build in debug mode
npm run build:debug
# Build in release mode
npm run build:release
node -e "const odin = require('./index.cjs'); console.log('ODIN SDK loaded:', !!odin.OdinClient);"
├── cppsrc/ # C++ native bindings source code
├── libs/
│ ├── bin/ # ODIN SDK binaries (all platforms)
│ │ ├── linux/ # Linux binaries (x64, arm64, ia32)
│ │ ├── macos/ # macOS binaries (arm64, x64, universal)
│ │ └── windows/ # Windows binaries (x64, ia32)
│ └── include/ # ODIN SDK headers (odin.h, odin_crypto.h)
├── index.cjs # JavaScript wrapper
├── *.d.ts # TypeScript type definitions
└── tests/ # Example scripts
We welcome contributions! Please see our Contributing Guide for details.
MIT License - see LICENSE for details.
Made with ❤️ by 4Players GmbH
FAQs
NodeJS bindings for the ODIN SDK. Use for AI enhanced human interactions, content moderation and audio processing features in a backend.
We found that @4players/odin-nodejs demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 2 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Product
Socket for Jira lets teams turn alerts into Jira tickets with manual creation, automated ticketing rules, and two-way sync.

Company News
Socket won two 2026 Reppy Awards from RepVue, ranking in the top 5% of all sales orgs. AE Alexandra Lister shares what it's like to grow a sales career here.

Security News
NIST will stop enriching most CVEs under a new risk-based model, narrowing the NVD's scope as vulnerability submissions continue to surge.