Skip to content

Core: Adapters & Dependency Injection

Nothing the kit does to a browser API is hardcoded. Every non-lifecycle behavior is a named interface with a working default, swappable at setup() with no subclassing.

The adapter ports

Each of these is a field on BasePlayerConfig, except stream sources, which you register with registerStream(). Supplying the field replaces the kit’s default outright; a partial override ({ ...browserPlatform, wakeLock: myWakeLock }) composes cleanly for interfaces that bundle several sub-controllers.

PortInterfaceDefaultSwap for
StorageIStorageLocalStorageBackendIndexedDBBackend, MemoryStorageBackend, a remote store
PlatformIPlatformbrowserPlatformCapacitor, Tauri, Electron, or a partial override
Stream sourceIStreamSource / IStreamFactorynative + HLS, registered at setupa DASH or custom-protocol factory via registerStream()
URL resolverIUrlResolverauth.transformUrl + structured parseCDN-specific signing, per-category routing
TranslatorITranslatorDefaultTranslatori18next, FormatJS, any pluralization-aware engine
Cue parserICueParserVTT, sprite-VTT, LRCTTML, SRT, a proprietary format
Preload strategyIPreloadStrategyDefaultPreloadStrategycustom prefetch timing or asset lists
Transition strategyITransitionStrategycrossfade (music) / gapless (video)a custom fade or cut
Shuffle strategyIShuffleStrategyFisherYatesShufflecursor-aware or weighted ordering
Realtime channelRealtimeFactorynativeWebSocketAdapterSignalR, Socket.IO, a custom transport

The queue’s cursor-follow logic, the crossfade timing math, the retry backoff, none of it needs to know which concrete implementation is active. Every mixin reads through the interface, so a swap changes behavior without touching kit code.

Composing a player from the ports

Three functions turn a plain class into something that behaves like NMVideoPlayer or NMMusicPlayer, and Quickstart shows all three together:

  • resolvePlayerConstructor(id, instances, className): the three-form factory resolution (nmplayer() / nmplayer('div-id') / nmplayer(2)) both real players’ static constructors use.
  • initPlayerCoreState(player, opts): seeds every internal slot (phase, queue, volume, plugin registry, and the rest) to its canonical starting value.
  • composeMixins(prototype, ...modules): stamps one or more mixin objects onto a prototype via Object.defineProperty, preserving getters and setters. playerCoreMethods is the full aggregate; later modules in the argument list override earlier ones on key collision.

Swapping one at setup

The example below replaces storage (so nothing touches localStorage) and shuffleStrategy (with a deterministic strategy, easy to verify) on the same minimal player composed in Quickstart.

TypeScript
/**
* Adapter ports are swapped at `setup()` — no subclassing, no forking the
* kit. This example replaces two of them: `storage` (default
* `LocalStorageBackend`) with the in-memory backend, and `shuffleStrategy`
* (default `FisherYatesShuffle`) with a deterministic strategy that's easy to
* verify. Every other port (`IPlatform`, `IStreamSource`, `IUrlResolver`,
* `ITranslator`, `ICueParser`, `IPreloadStrategy`, `ITransitionStrategy`,
* `RealtimeFactory`) swaps the same way: name the field in `setup({...})`.
*/

import type {
BasePlaylistItem,
IShuffleStrategy,
} from '@nomercy-entertainment/nomercy-player-core';
import { MemoryStorageBackend } from '@nomercy-entertainment/nomercy-player-core';
import { tourPlayer } from './tour-player';

// A deterministic strategy is easy to demonstrate — reverses the queue
// instead of randomising it. Real strategies commonly pin the current item
// and shuffle only the remainder; the cursor-follow logic is handled by
// MediaList automatically for any IShuffleStrategy implementation.
const reverseShuffle: IShuffleStrategy = {
order: (items) => [...items].reverse(),
};

const episodes: BasePlaylistItem[] = [
{ id: 'ep-1', title: 'Episode 1' },
{ id: 'ep-2', title: 'Episode 2' },
{ id: 'ep-3', title: 'Episode 3' },
];

const player = tourPlayer('adapters-demo');

player.setup({
logLevel: 'info',
storage: new MemoryStorageBackend(), // no localStorage writes — safe for SSR and tests
shuffleStrategy: reverseShuffle,
});
await player.ready();

player.queue(episodes);
player.queueShuffle();

console.log(player.queue().map((item) => item.id)); // ['ep-3', 'ep-2', 'ep-1'] — the custom strategy ran

await player.dispose();

Next steps

  • i18n: the default translator this same swap mechanism replaces when you need pluralization or ICU MessageFormat.