npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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

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

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


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:


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:

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

Contributing

We welcome contributions! Please see our Contributing Guide for details.

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Submit a pull request

Support


License

MIT License - see LICENSE for details.