Skip to main content
Version: 0.11.x

ODIN Node.js SDK

npm version License: MIT Discord

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-nodejs

Prerequisites

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();

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.

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

MethodDescription
generateToken(accessKey, roomId, userId)Generate a room token locally
createRoom(token)Create a room instance (recommended)
createRoomWithToken(token)Alias for createRoom

OdinRoom

MethodDescription
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

PropertyTypeDescription
ownPeerIdnumberYour peer ID
connectedbooleanConnection status
availableMediaIdsnumber[]Available media IDs for audio streams

OdinMedia (Audio Stream)

MethodDescription
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)

MethodDescription
setPassword(password)Set encryption password
getPeerStatus(peerId)Get peer's encryption status

Events

EventPayload
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

FeatureNode.js SDKWeb SDK
PlatformNode.js (server)Browser
PerformanceNative C++WebRTC/JavaScript
Raw Audio Access✅ Full PCM data⚠️ Web Audio API
Use CasesBots, recording, AIClient 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:


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-tools

macOS Security Warnings

If you see "code signature not valid" errors:

cd node_modules/@4players/odin-nodejs/build/Debug
xattr -cr *.dylib
codesign -f -s - *.dylib

Connection Issues

  1. Verify your access key is correct
  2. Check your network allows WebSocket connections
  3. 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-tools

2. Download ODIN SDK Libraries

Download the ODIN SDK libraries from the official releases:

  1. Download the appropriate archive for your platform from the release assets
  2. Extract the libraries to the correct location:
PlatformArchitectureTarget Directory
Linuxx64libs/bin/linux/x64/
Linuxarm64libs/bin/linux/arm64/
Linuxia32libs/bin/linux/ia32/
macOSUniversallibs/bin/macos/universal/
Windowsx64libs/bin/windows/x64/
Windowsia32libs/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:release

4. 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 scripts

Support


License

MIT License - see LICENSE for details.


Made with ❤️ by 4Players GmbH