@4players/odin-nodejs
v0.11.2
Published
NodeJS bindings for the ODIN SDK. Use for AI enhanced human interactions, content moderation and audio processing features in a backend.
Downloads
1,218
Readme
ODIN Node.js SDK
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
Features
- 🎙️ Real-time Voice Chat - Low-latency voice communication
- 🔐 End-to-End Encryption - Built-in E2EE with OdinCipher
- 🤖 Bot Integration - Perfect for recording bots, AI assistants, and moderation tools
- 📊 Raw Audio Access - Get PCM audio data for processing, recording, or transcription
- 🌍 Proximity Chat - 3D positional audio support
- ⚡ High Performance - Native C++ bindings for maximum efficiency
- 📈 Diagnostics - Real-time connection and audio quality monitoring
Installation
npm install @4players/odin-nodejsPrerequisites
This SDK includes prebuilt binaries for:
- macOS (x86_64 and arm64)
- Windows (x86_64)
- Linux (x86_64)
For other platforms, you'll need a C++ compiler. See node-gyp requirements.
Quick Start
1. Get Your Access Key
Sign up at 4Players ODIN to get your free access key.
2. Basic Connection Example
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();Docker Usage
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-exampleEnvironment variables supported by the example:
ODIN_ACCESS_KEY(required) – access key used to mint tokens.ODIN_ROOM_ID(optional) – defaults toodin-sdk-ci-test.ODIN_USER_ID(optional) – auto-generates a random ID when omitted.ODIN_GATEWAY(optional) – defaults tohttps://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.
Event Handlers
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 }
});Audio Recording Example
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();Sending Audio
The SDK provides two approaches for sending audio: a high-level API for convenience and a low-level API for full control.
High-Level API (Recommended)
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();Low-Level API
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.
End-to-End Encryption (E2EE)
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.
Verifying Peer Encryption Status
// 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"Proximity Chat (3D Audio)
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
});Connection Diagnostics
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}`);
}
});API Reference
OdinClient
| Method | Description |
|--------|-------------|
| generateToken(accessKey, roomId, userId) | Generate a room token locally |
| createRoom(token) | Create a room instance (recommended) |
| createRoomWithToken(token) | Alias for createRoom |
OdinRoom
| 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 |
OdinRoom Properties
| Property | Type | Description |
|----------|------|-------------|
| ownPeerId | number | Your peer ID |
| connected | boolean | Connection status |
| availableMediaIds | number[] | Available media IDs for audio streams |
OdinMedia (Audio Stream)
| 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) |
OdinCipher (E2EE)
| Method | Description |
|--------|-------------|
| setPassword(password) | Set encryption password |
| getPeerStatus(peerId) | Get peer's encryption status |
Events
| 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 } |
Comparison with Web SDK
| 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:
- 🎙️ Audio recording bots
- 🤖 AI-powered voice assistants
- 📝 Speech-to-text transcription
- 🛡️ Content moderation
- 🔊 Audio processing pipelines
Examples
Check the tests/ folder for complete examples:
- connection-test - Basic connection, events, and diagnostics
- audio-recording - Recording peer audio to WAV files
- sending-audio - Sending audio with both high-level and low-level APIs
Troubleshooting
Build Errors
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-toolsmacOS Security Warnings
If you see "code signature not valid" errors:
cd node_modules/@4players/odin-nodejs/build/Debug
xattr -cr *.dylib
codesign -f -s - *.dylibConnection Issues
- Verify your access key is correct
- Check your network allows WebSocket connections
- Ensure the token hasn't expired
Development
Building for Other Platforms
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:
1. Install Build Requirements
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-tools2. Download ODIN SDK Libraries
Download the ODIN SDK libraries from the official releases:
- Download the appropriate archive for your platform from the release assets
- Extract the libraries to the correct location:
| 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:
- Linux:
libodin_static.a,libodin.so,libodin_crypto_static.a,libodin_crypto.so - macOS:
libodin.dylib,libodin_crypto.dylib,libodin_static.a,libodin_crypto_static.a - Windows:
odin_static.lib,odin.dll,odin_crypto_static.lib,odin_crypto.dll
3. Build the Native Module
# Build in debug mode
npm run build:debug
# Build in release mode
npm run build:release4. Verify the Build
node -e "const odin = require('./index.cjs'); console.log('ODIN SDK loaded:', !!odin.OdinClient);"Project Structure
├── 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 scriptsContributing
We welcome contributions! Please see our Contributing Guide for details.
- Fork the repository
- Create a feature branch
- Make your changes
- Submit a pull request
Support
- 📖 Documentation: docs.4players.io
- 💬 Discord: Join our community
- 📧 Email: [email protected]
- 🐛 Issues: GitHub Issues
License
MIT License - see LICENSE for details.
