Developer Documentation
Our new developer documentation is now available. Please check it out!

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.

Source Code

The web SDK is developed in TypeScript and open source. Check out the source in our public Github repository.

Installation

You can install the ODIN web SDK in your NodeJS project (you may want to build a bot in your NodeJS based server that responds to messages sent by users), you may add ODIN to your Angular, React, React Native, Svelte or any other frontend framework based on NPM.

NPM

npm npm npm

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

npm install --save @4players/odin

HTML

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

<script type="text/javascript" src="https://4npcdn.com/2355/odin/javascript/latest/odin.min.js"></script>

We also provide versioned URLs like this:

<script type="text/javascript" src="https://4npcdn.com/2355/odin/javascript/0.9.0/odin.min.js"></script>

If you want to host it yourself or make adjustments, you can also build it from source:

  1. Clone this repo on your workstation: https://github.com/4Players/odin-sdk-web
  2. Install modules: npm install
  3. Build vanilla JavaScript script: npm run bundle
  4. Congratulations! You now have a bundled script file named odin.min.js in the dist folder.

You can now use the code as shown in the sample below, with one exception:

In the NPM package, namespaces and modules exist to separate different APIs from each other so they don’t share the same space and accidentally overwrite things from other APIs. As some browsers don’t support modules, we group ODIN APIs in a global namespace named ODIN. The namespace is defined in the Rollup bundle settings (please refer to rollup.config.ts for details).

Therefore, whenever you see something in the docs that says OdinClient.initRoom you’ll need to change that to ODIN.OdinClient.initRoom in vanilla JavaScript.

Interoperability

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

While next-gen browsers and native ODIN SDKs utilize 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.

Event Handling

The ODIN server will automatically notify you about relevant updates and the ODIN web SDK is using the JavaScript EventTarget API to dispatch custom events based on the IOdinEvents interface. Use any of the provided the addEventListener methods to set up a function that will be called whenever the specified event is delivered to the target.

Events are available in the following scopes:

  • IOdinClientEvents
  • IOdinRoomEvents
  • IOdinPeerEvents
  • IOdinMediaEvents

This allows fine-grained control over which events you want to receive on any specific class instance, most notably OdinRoom . Please refer to the scopes listed above to find out which events are available.

anyOdinObjectInstance.addEventListener('<EVENT_NAME>', function(event) { /* do something */ });

Using Media objects

ODIN works with the concept of media objects that are attached to a room. A media is either remote (i.e. connected to the mic of someone else device) or a local media, which is linked to your own microphone or other input device.

After joining a room, you can create a media (linked to your local input device) and add to the room. After joining a room, you are just a listener in the room, you don’t send anything to it (i.e. your are muted). If you want to unmute the local user, you need to attach a media to that room:

Creating a media object
// Create a new audio stream for our default capture device and append it to the room
const mediaStream = await navigator.mediaDevices.getUserMedia({
  echoCancellation: true,
  autoGainControl: true,
  noiseSuppression: true,
});

// Create and append a new input media to the room
const inputMedia = await odinRoom.createMedia(mediaStream);

// Start transmitting voice data (this can also be done later in the event listener for 'MediaStarted')
inputMedia.start();

That’s it. The navigator objects mediaDevices has many functions to identify the input devices available on the users device, i.t. you could show users a list of devices with all mics attached to the users device before attaching that media.

Warning

Important: In our Web SDK, media objects are not started immediately. The objects are created, but they don’t send any data yet. You need to call the start method on the media object to start sending (and receiving data).

If you also want to enable receiving voice from others, you’ll need to start incoming media objects, too. For this, add an event handler for the MediaStarted event like this:

Starting incoming media objects
  // Listen to media started events in the room and start decoding its voice packets
odinRoom.addEventListener('MediaStarted', (event) => {
    console.log(`Peer ${event.payload.peer.id} added a new media stream to the room`);
    // Start the media stream to enable the speaker for this media stream.
    event.payload.media.start();
});

Disconnect from Odin

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

room.disconnect(); // room: OdinRoom

This function will stop all media objects from sending and receiving anymore data. However, the browser may continue showing a microphone indicator on the tab, which can mistakenly suggest that the microphone is still being used. To remove this indicator and let the browser know, that the microphone is no longer active, implement the clean-up code as shown in the example below:

Clean-up media tracks and hide microphone indicator
function stopMediaStream(ms: MediaStream) {
  ms.getTracks().forEach((track) => {
    track.stop();
    ms.removeTrack(track);
  });
}

Execute this clean-up function on the media stream that is capturing audio to make sure that all resources are properly released and the microphone indicator is removed.

Examples

We have prepared a couple of examples for various web frameworks.

Vanilla JS

This example shows how to join a room and how to add a media stream (i.e. microphone) to it. It also shows how to subscribe on important events and how to manage them. This can be useful to update lists of users in the channel or to show activity indicators.

To join an ODIN room, you need to create an access token. This should not be done on client side as the access key is very powerful and should not be exposed in a public client. We provide a simple to use and deploy token server via NodeJS that you can use to create ODIN tokens - you can also use our NPM package @4players/odin-tokens to add a route to your existing NodeJS based server or cloud functions. Check out the token server here: Token Server NodeJS Example.

The code should be self-explanatory, however, the basic steps are:

  1. Receive an access token for the room default from our example token server
  2. Join the room with that access token
  3. Setup listeners to receive notifications if new users join the room or start and stop talking
Setup ODIN and join room
import { OdinClient } from '@4players/odin';

/**
 * Receives a token from our token server example which is required to join a room on an Odin server
 * @see https://developers.4players.io/odin/examples/token-server/
 * @param roomId The id (i.e. name) of the room to join
 * @param userId The id (i.e. name) of the user to join
 */
const getToken = async function (roomId: string, userId: string): Promise<string | null> {
  // Get a token from our token server example that you can find here:
  const token = await fetch('http://0.0.0.0:8080/default?user_id=john')
    .then((response) => {
      if (response.status !== 200) {
        console.log('Looks like there was a problem. Status Code: ' + response.status);
        return null;
      }

      // Examine the text in the response
      return response.json().then(function (data) {
        return data.token;
      });
    })
    .catch(function (err) {
      console.log('Fetch Error :-S', err);
      return null;
    });

  return token;
};

/**
 * Starts an ODIN session and joins the user with name John to the room default
 */
const startOdin = async function () {
  // User ID can be anything, but it must be unique. You can also use UserData to set profile name, avatars, etc. and set a unique user identifier.
  const token = await getToken('My Fancy Room', 'John Doe');
  if (!token) {
    console.error('Could not get token, make sure token server is running');
    return;
  }

  // Now that we have the token, we can initialize a room instance and create listeners on important callbacks
  const odinRoom = await OdinClient.initRoom(token);

  // Handle events for new peers joining the room
  odinRoom.addEventListener('PeerJoined', (event) => {
    console.log(`Peer ${event.payload.peer.id} joined`);
  });

  // Handle events for peers leaving the room
  odinRoom.addEventListener('PeerLeft', (event) => {
    console.log(`Peer ${event.payload.peer.id} left`);
  });

  // Handle events for medias added (e.g. start processing voice data)
  odinRoom.addEventListener('MediaStarted', (event) => {
    event.payload.media.start();
  });

  // Handle events for medias removed (e.g. stop processing voice data)
  odinRoom.addEventListener('MediaStopped', (event) => {
    event.payload.media.stop();
  });

  // Handle events for media activity (e.g. user starts/stops talking)
  odinRoom.addEventListener('MediaActivity', (event) => {
    console.log(`Peer ${event.payload.peer.id} ${event.payload.media.active ? 'started' : 'stopped'} talking`);
  });

  // Once all the listeners are set, we join the room
  await odinRoom.join();

  // Create a new audio stream for our default capture device and append it to the room
  navigator.mediaDevices.getUserMedia({ audio: true }).then(async (mediaStream) => {
    odinRoom.createMedia(mediaStream);
    // The mic is enabled, and the media object is created, but it's muted, i.e. not sending data.
    // Start the media stream to unmute the user:
    await mediaStream.start();
  });
};

startOdin()
  .then(() => {
    console.log('Room joined successfully');
  })
  .catch((error) => {
    console.log('Failed to join room:', error);
  });

Angular

We have created a simple Angular application that you can use as a starting point for your own Angular application or an integration into existing applications.

We provide the source code in our Github repository: Angular Demo for ODIN.