Skip to main content

ODIN Voice Web SDK

ODIN is our cross-platform immersive voice SDK with best-in-class quality and noise suppression technologies. You can integrate it with ease in your TypeScript/JavaScript-based applications. The SDK gets initialized by providing a compatible Voice Plugin.

The ODIN web SDK is fully compatible with our other client SDKs, so you can communicate between your favorite web browser and any native app or game that integrates ODIN.

While next-gen browsers and native ODIN SDKs use WebTransport over HTTP/3 to talk to an ODIN server using a secure and reliable multiplexed transport, we've also implemented a fallback mechanism over WebRTC data channels for browsers without HTTP/3 support.

Installation

You can install the ODIN web SDK in your NodeJS project to use it with any modern JavaScript framework or include it directly in your HTML.

NPM

Install the web SDK and Plugin via npm in your project like this:

npm npm npm

npm i @4players/odin

npm npm npm

npm i @4players/odin-plugin-web

HTML

If you want to include ODIN in your website, you can add ODIN with this script tag to your website:

IIFE
<script type="text/javascript" src="https://cdn.odin.4players.io/client/js/sdk/1.0.0-alpha.51/odin-sdk.js"></script>
<script type="text/javascript" src="https://cdn.odin.4players.io/client/js/sdk/1.0.0-alpha.57/odin-plugin.js"></script>
ESM
<script type="text/javascript" src="https://cdn.odin.4players.io/client/js/sdk/1.0.0-alpha.51/odin-sdk.esm.js"></script>
<script type="text/javascript" src="https://cdn.odin.4players.io/client/js/sdk/1.0.0-alpha.57/odin-plugin.esm.js"></script>

You can now use the code as shown in the samples.

Usage

To use and interact with the ODIN WebSDK, there are a few general steps that every implementation will need.

  • Initializing the Voice Plugin (@4players/odin-plugin-web)
  • Fetching the Token from your backend
  • Creating a room and registering EventHandlers
  • Joining a Room with the provided Token
  • Starting an AudioInput to be able to talk

Below, these steps are explained in detail.

info

The following guideline is built upon each other. Imports of JavaScript code are not included because they might differ on how the implementation was done. For more complete examples, please check our respective examples.

Initializing the Audio Plugin

Initialice the audio plugin
    let audioPlugin; // Should persist the session

/**
* Helper function to intialize the Audio Plugin.
* @returns {Promise<Plugin>}
*/
async function initPlugin() {
if (audioPlugin) return audioPlugin;
// ODIN_PLUGIN is the Odin Web Plugin
audioPlugin = ODIN_PLUGIN.createPlugin(async (sampleRate) => {
const audioContext = new AudioContext({ sampleRate });
await audioContext.resume();
return audioContext;
});
// ODIN is the Odin WebSDK
ODIN.init(audioPlugin);
return audioPlugin;
}

initPlugin().then(_ => {
console.log('WebSDK initalized, have fun!');
});

Fetching a token and creating a room

Before a client can join a room, two things need to happen: the room instance itself must be created, and an authentication token must be fetched from your backend server.

It's crucial that these tokens are generated and served by your backend. This is because the accessKey required to create valid ODIN tokens is a sensitive credential that should never be exposed directly to the public (e.g., in client-side code). To simplify the process of generating these tokens on your backend, we provide an npm package specifically for this purpose.

Creating a token on the server
import { TokenGenerator } from "@4players/odin-tokens";

const accessKey = "<YOUR API KEY HERE>";
const generator = new TokenGenerator(accessKey);
const token = await generator.createToken("my room", "john doe");

console.log(`generated a new token: ${token}`);
Fetching a token and creating a Room instance on the client
let room; // It often makes sense, to have the room in a scope where the app can access it from different places.
async function join() {
try {
const token = fetchToken(); // Providing a token
room = new Room();
} catch (e) {
// handle error, ui etc.
}
}

Handling Events

The ODIN server will automatically notify you about relevant updates, and the ODIN web SDK uses the JavaScript EventTarget API to dispatch custom events based on the

OdinEventTarget

. Use any of the provided addEventListener methods to set up a function that will be called whenever the specified event is delivered to the target.

room.addEventListener('PeerJoined', callback)
info

To prevent memory leaks when reusing a room instance for multiple connections, ensure that EventListeners are handled correctly. You should either:

  • Remove event listeners when leaving the room.
  • Ensure that event listeners are registered only once.

Alternatively, you can use the room.onPeerJoined syntax. This assigns a callback directly, overwriting any previous assignment, which can simplify event management and help avoid unintended multiple registrations.

Besides using room.addEventListener, there's a simpler, alternative method for handling events. For every available event, you can assign a callback function directly to a corresponding property, similar to how events are handled on many standard Web API objects in the browser.

room.onPeerJoined = callback

Events are available in the following scopes:

This allows fine-grained control over which events you want to receive on any specific class instance, most notably

Room

. Please refer to the scopes listed above to find out which events are available.

All Events are available as on the instances via properties too.

Registering event handlers
let room;
async function join() {
try {
const token = fetchToken(); // Providing a token
room = new Room();
room.addEventListener('EventName', callback)
// on[EventName]
room.onPeerJoined = callback
// It might be useful to have a method where all the Events are registered.
roomEventHandlers(room);
} catch (e) {
// handle error, ui etc.
}
}

Joining a room

Once the token was fetched, the room instance created and room events registered, we can join the room. EventHandlers (Callbacks) like room.onJoined, room.onPeerJoined, room.onStatusChanged will get called when joining a room.

Joining a room
let room;
async function join() {
try {
const token = fetchToken(); // Providing a token
room = new Room();
roomEventHandlers(room);
await room.join(token, {
gateway: 'https://gateway.odin.4players.io' // Optionally, by default joining the EU Gateway.
});
} catch (e) {
// handle error, ui etc.
}
}

Starting Audio

The ODIN SDK uses instances of

AudioInput

to represent an audio capture source, such as a microphone.

While an AudioInput can be created at any point, this example demonstrates creating it after successfully joining a room for simplicity. To be able to speak in a room, an

AudioInput

needs to get attached to a room via room.addAudioInput(audioInput).

To hear other peers in the room, you must configure an output device. This is done once by calling audioPlugin.setOutputDevice({}) on the AudioPlugin instance. Since the output device setting is global to the audio plugin, the method is called directly on the plugin object.

Starting audio
let room;
let audioInput; // Also added to a wider scope for access in the app
async function join() {
try {
const token = fetchToken(); // Providing a token
room = new Room();
roomEventHandlers(room);
await room.join();
startAudio();
} catch (e) {
// handle error, ui etc.
}
}

async function startAudio() {
audioPlugin.setOutputDevice({}).then();
// Usually, only one AudioInput is needed during a session.
// Even when another room was joined, the same AudioInput can be used.
if (!audioInput) {
audioInput = await DeviceManager.createAudioInput();
};
room.addAudioInput(audioInput);
}
info

Important Note: Both audioPlugin.setOutputDevice({}) and room.AddAudioInput() will attempt to start the browser's AudioContext. Modern browsers require a direct user interaction (like a click or tap) to allow an AudioContext to start. Therefore, it's best practice to call these methods from within a function that is triggered by a user action. For instance, invoking them in the same function that handles the user's action to join a room, as demonstrated in the example, is a recommended approach.

warning

Important Note: Audio is only transmitted, after an AudioInput was attached to a room by using room.addAudioInput(audioInput).

Leaving a room

To disconnect a player from an Odin room, you can call:

room.leave();
info

While a Room instance can technically be reused for multiple connections, it is often simpler to create a new Room instance each time you connect.

In contrast, an AudioInput instance is independent of the Room's lifecycle and should typically be reused across connections.

Examples