Skip to main content

Customize the Plugin

The SDK automatically registers @4players/odin-plugin-web as its default audio plugin when needed -- no manual initialization is required for most use cases. This page explains how to customize the plugin when the defaults are not sufficient, and documents the Plugin API interface for advanced use cases.

Registering a custom plugin

If you want to customize the plugin (e.g. to enable logging or disable auto-reconnect), create one manually using createPlugin() from @4players/odin-plugin-web and register it with ensurePlugin() from @4players/odin:

Register a custom plugin
import { ensurePlugin } from '@4players/odin';
import { createPlugin } from '@4players/odin-plugin-web';

const plugin = createPlugin(
async (sampleRate) => {
const audioContext = new AudioContext({ sampleRate });
await audioContext.resume();
return audioContext;
},
{
onLogMessage: (formattedString, record) => {
console.log(formattedString, record);
},
}
);
ensurePlugin(plugin);
warning

If you call ensurePlugin() with a custom plugin, do so before any other SDK call (e.g. before setOutputDevice() or room.join()). If no custom plugin is registered, the SDK will create and use a default one automatically.

Replacing the active plugin

If a plugin is already registered and you need to swap it out (e.g. during testing or to change configuration at runtime), use replacePlugin():

import { replacePlugin } from '@4players/odin';

replacePlugin(myNewPlugin);

Unlike ensurePlugin, this always overwrites the current plugin, even if one is already active.

createPlugin parameters

The createPlugin function from @4players/odin-plugin-web accepts two arguments:

function createPlugin(
audioContextFactory: AudioContextFactory,
options?: WebPluginOptions
): WebPlugin;

audioContextFactory

A factory function the SDK calls whenever it needs an AudioContext. It receives the desired sample rate and must return a Promise<AudioContext>:

type AudioContextFactory = (sampleRate: number | undefined) => Promise<AudioContext>;

The default plugin creates this factory for you automatically. When providing your own, make sure to call audioContext.resume() inside the factory, as browsers require this to start audio processing.

options (WebPluginOptions)

An optional configuration object with the following properties:

PropertyTypeDefaultDescription
onLogMessage(formattedString: string, record: LogRecord) => voidundefinedCallback invoked for every internal log message. The formattedString is a pre-formatted human-readable string; record contains structured data (level, timestamp, logger name, message).
loggerLoggerBuilt-in loggerA custom Logger instance from @4players/odin-common. When provided, it replaces the built-in logger entirely. Most use cases should use onLogMessage instead.
disableAutoReconnectbooleanfalseWhen true, the plugin will not automatically attempt to reconnect after a connection loss. Useful if your application implements its own reconnection logic.
disableBeforeUnloadbooleanfalseWhen true, the plugin will not register a beforeunload handler on the window object. By default, the plugin auto-closes all connections when the page is being unloaded.

Logging example

import { ensurePlugin } from '@4players/odin';
import { createPlugin } from '@4players/odin-plugin-web';

const plugin = createPlugin(
async (sampleRate) => {
const audioContext = new AudioContext({ sampleRate });
await audioContext.resume();
return audioContext;
},
{
onLogMessage: (formattedString, record) => {
// Forward ODIN plugin logs to your logging system
console.log(formattedString, record);
},
}
);
ensurePlugin(plugin);

Disabling auto-reconnect

const plugin = createPlugin(
async (sampleRate) => {
const audioContext = new AudioContext({ sampleRate });
await audioContext.resume();
return audioContext;
},
{
disableAutoReconnect: true,
}
);
ensurePlugin(plugin);

Plugin API

The SDK defines a Plugin interface that any plugin must implement. The built-in web plugin (@4players/odin-plugin-web) implements this interface, but you can also build a custom plugin as long as it conforms to the same contract.

interface Plugin {
readonly version: Version; // Must be '8' for the current SDK
readonly playbackVolume: PlaybackVolume; // Current global playback volume [left, right]
readonly supportedTransports: Transport[];// e.g. ['H3', 'WebRTC']

joinRoom(parameters: JoinRoomParameters): Room;

createAudioPlayback(parameters: CreateAudioPlaybackParameters): Promise<AudioPlayback>;
createAudioCapture(parameters: CreateAudioCaptureParameters): Promise<AudioCapture>;

createVideoCapture(ms: MediaStream, options?: VideoInputOptions): Promise<VideoCapture>;
createVideoPlayback(parameters: CreateVideoPlaybackParameters): Promise<VideoPlayback>;

enumerateDevices(): Promise<Array<Device>>;

setOutputVolume(volume: PlaybackVolume): void;
setOutputDevice(device: DeviceParameters): Promise<void>;

close(): void;
}

Key types

type Version = '8';
type Transport = 'H3' | 'WebRTC';
type PlaybackVolume = [number, number]; // [left, right] channel volume

interface Device {
readonly type: 'AudioPlayback' | 'AudioCapture';
readonly name: string;
readonly id: string;
readonly isDefault: boolean;
}

interface DeviceParameters {
readonly device?: Device;
onStatusChanged?: (status: 'started' | 'stopped') => void;
}

Implementing a custom plugin

A custom plugin must set version to '8' (the current SDK API version) and implement all methods of the interface. The SDK validates the version when the plugin is registered and will throw an error if it does not match.

import { ensurePlugin } from '@4players/odin';

const myCustomPlugin = {
version: '8',
playbackVolume: [1, 1],
supportedTransports: ['H3'],
joinRoom(params) { /* ... */ },
createAudioPlayback(params) { /* ... */ },
createAudioCapture(params) { /* ... */ },
createVideoCapture(ms, options) { /* ... */ },
createVideoPlayback(params) { /* ... */ },
enumerateDevices() { /* ... */ },
setOutputVolume(volume) { /* ... */ },
setOutputDevice(device) { /* ... */ },
close() { /* ... */ },
};

ensurePlugin(myCustomPlugin);
note

A native Node.js plugin for server-side and non-browser environments is currently in development. This will allow running ODIN audio processing natively without a browser AudioContext.