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.
| Port | Interface | Default | Swap for |
|---|---|---|---|
| Storage | IStorage | LocalStorageBackend | IndexedDBBackend, MemoryStorageBackend, a remote store |
| Platform | IPlatform | browserPlatform | Capacitor, Tauri, Electron, or a partial override |
| Stream source | IStreamSource / IStreamFactory | native + HLS, registered at setup | a DASH or custom-protocol factory via registerStream() |
| URL resolver | IUrlResolver | auth.transformUrl + structured parse | CDN-specific signing, per-category routing |
| Translator | ITranslator | DefaultTranslator | i18next, FormatJS, any pluralization-aware engine |
| Cue parser | ICueParser | VTT, sprite-VTT, LRC | TTML, SRT, a proprietary format |
| Preload strategy | IPreloadStrategy | DefaultPreloadStrategy | custom prefetch timing or asset lists |
| Transition strategy | ITransitionStrategy | crossfade (music) / gapless (video) | a custom fade or cut |
| Shuffle strategy | IShuffleStrategy | FisherYatesShuffle | cursor-aware or weighted ordering |
| Realtime channel | RealtimeFactory | nativeWebSocketAdapter | SignalR, 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 viaObject.defineProperty, preserving getters and setters.playerCoreMethodsis 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.
/**
* 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.