Configuration Reference
All options passed to nmplayer(id).setup(opts).
Fields marked with a default show the value used when the option is omitted.
Container
The container is not a setup() option — it is the element you pass to the nmplayer(id) factory, looked up by its id. The player mounts into that element. UI plugins (DesktopUiPlugin, OctopusPlugin, SubtitleOverlayPlugin) render into it; the bare player adds only the <video> element.
Playlist / queue
playlist
playlist?: T[] | string
Initial queue.
Pass an array of VideoPlaylistItem-compatible objects, or a URL to fetch a JSON playlist.
// Inline items
player.setup({ playlist: [{ id: '1', url: '...', title: 'My Video' }] });
// URL-based, fetched with auth during setup
player.setup({ playlist: 'https://api.example.com/playlist.json' });
When a URL is provided, the player emits playlistResolving → playlistReady (or playlistError).
The player always reaches ready, so if the fetch fails, it starts with an empty queue.
autoPlay
autoPlay?: boolean
Start playback when the first item’s media is ready (the mediaReady event). Requires a non-empty playlist at setup time. Setup resolving alone does not trigger playback; the player waits until media is ready to decode. Default false.
muted
muted?: boolean
Start muted. Default false.
preload
preload?: 'auto' | 'metadata' | 'none'
Intended for the <video> element’s preload attribute, but not yet read by the player, so the element keeps the browser default. Like playbackRates, the field is accepted today but currently inert.
autoAdvance
autoAdvance?: boolean
When the current item ends, advance to the next item in the queue automatically. Default: true. Set to false when an external orchestrator (Cast sync, a NoMercy Connect socket) drives next() itself, so an item is never advanced twice. With RepeatState.ONE active, the player restarts the current item instead of advancing.
Display
controls
controls?: boolean
Show the browser’s native video controls.
Default false.
Do not combine with DesktopUiPlugin, the built-in UI replaces native controls.
stretching
stretching?: 'uniform' | 'fill' | 'exactfit' | 'none'
How the video fills its container.
Default 'uniform' (letterbox/pillarbox, preserving aspect ratio).
| Value | Behavior |
|---|---|
'uniform' | Preserve aspect ratio, fit inside container |
'fill' | Stretch to fill, ignore aspect ratio |
'exactfit' | Cover-crop to fill, preserving aspect ratio (may clip edges) |
'none' | No scaling applied |
theaterDefault
theaterDefault?: boolean
Start in theater mode. Default false.
Playback rates
playbackRates
playbackRates?: number[]
The intended list of selectable playback rates. Note: the player currently uses a fixed list ([0.5, 0.75, 1, 1.25, 1.5, 2]) and does not yet read this option, so setting it has no effect today.
Language and defaults
defaultSubtitleLanguage
defaultSubtitleLanguage?: string
BCP-47 language tag.
The player auto-selects the first subtitle track matching this language when a new item loads.
Example: 'en', 'nl', 'ja'.
defaultAudioLanguage
defaultAudioLanguage?: string
BCP-47 language tag. Auto-selects the audio track matching this language. Falls back to the first available track.
defaultQuality
defaultQuality?: 'auto' | number
Initial quality level.
'auto' enables hls.js ABR.
A number sets a fixed level index.
Default: 'auto'.
Image URLs
baseImageUrl
baseImageUrl?: string
Base URL prepended to relative image, poster, and thumbnail paths on playlist items.
Useful for TMDB-style paths:
player.setup({
baseImageUrl: 'https://image.tmdb.org/t/p/original',
playlist: [{ id: '1', url: '...', poster: '/abc123.jpg' }],
// poster resolves to: https://image.tmdb.org/t/p/original/abc123.jpg
});
Absolute URLs (any scheme) pass through unchanged.
Auth
auth
type AuthHeaderValue = string | (() => string) | (() => Promise<string>);
auth?: {
bearerToken?: AuthHeaderValue;
refreshOnUnauthenticated?: () => Promise<void>;
signRequest?: (request: Request) => Request | Promise<Request>;
headers?: Record<string, AuthHeaderValue>;
credentials?: 'omit' | 'same-origin' | 'include';
transformUrl?: (url: string) => string | Promise<string>;
}
Auth pipeline for HLS manifest and segment requests.
On 401, refreshOnUnauthenticated fires once and the request is retried.
403 propagates immediately.
See Core: Auth & Fetch for full details.
Backend
backendFactory
backendFactory?: (kind: VideoBackendKind, config: VideoPlayerConfig) => IVideoBackend
Custom video backend factory.
Overrides the default Html5VideoBackend.
Use to wire in a WebCodecs backend, a native-shell bridge, or a test stub.
See Video: Adapters for alternatives.
Crossfade
crossfadeEnabled
crossfadeEnabled?: boolean
Enable the crossfade/overlap transition between queue items. When false (the video default), the player uses a hard-cut gapless transition: assets are preloaded but there is no audio overlap. The music player default is true. Set to true to enable sample-accurate crossfade in the video player (requires a compatible ITransitionStrategy).
crossfadeLeadSeconds
crossfadeLeadSeconds?: number
How many seconds before the outgoing item ends the player begins the crossfade window. Only used when crossfadeEnabled: true. Default 3.
crossfadeTailSeconds
crossfadeTailSeconds?: number
How many seconds the incoming item plays in parallel with the outgoing item before the outgoing item is fully silent. Only used when crossfadeEnabled: true. Default 3.
Storage
storage
storage?: IStorage
Plugin state persistence backend.
Defaults to LocalStorageBackend.
See Core: Adapters for alternatives.
Logging
logLevel
logLevel?: 'silent' | 'error' | 'warn' | 'info' | 'debug' | 'trace'
Log verbosity.
Default: 'info'.
Set to 'debug' to see plugin-level events.
logger
logger?: ILogger
Custom logger. Defaults to the player core’s console logger. See Core: Adapters for alternatives.
debug
debug?: boolean
Deprecated alias for logLevel: 'debug'.
Use logLevel instead.
Platform
platform
platform?: IPlatform
Platform abstraction bundle.
Defaults to browserPlatform.
See Core: Adapters for alternatives.
Override individual sub-ports for Capacitor, Tauri, or Electron:
import { browserPlatform } from '@nomercy-entertainment/nomercy-player-core';
player.setup({
platform: { ...browserPlatform, wakeLock: myNativeWakeLock },
});
i18n
language
language?: string
Initial language (BCP-47). Default: navigator.language.
translations
translations?: Record<string, Record<string, string>>
Translation bundles keyed by BCP-47 language tag. The kit’s English bundle is always merged underneath as the final fallback.
Each inner record maps translation keys to translated strings for that language. Pass multiple language objects to support language switching without network requests:
import { defaultTranslations } from '@nomercy-entertainment/nomercy-player-core';
player.setup({
language: 'nl',
translations: {
...defaultTranslations,
nl: {
'core.policy.autoplayBlocked': 'Tik of klik ergens om te starten.',
'core.auth.forbidden': 'Je account heeft geen toegang tot deze inhoud.',
},
},
});
defaultTranslations is the pre-shipped English bundle re-exported from the core package.
Spreading it ensures English strings are always available as a fallback.
loadTranslations
loadTranslations?: (lang: string) => Promise<Record<string, string> | undefined>
Async loader called by player.language(lang) when the bundle for that language is not already loaded.
Return undefined to fall through to the static translations config.
player.setup({
language: 'nl',
loadTranslations: async (lang) => {
const module = await import(`./locales/${lang}.json`);
return module.default;
},
});
Use player.t('key') anywhere to look up a translation.
The resolution order is: loaded bundle for the active language -> English fallback -> onMissingTranslation handler -> key as-is.
Metrics
metricsIntervalMs
metricsIntervalMs?: number
Interval for the playback:metrics event (ms).
Default: 10000.
Set to 0 to disable.
progressIntervalMs
progressIntervalMs?: number
Interval for the progress event (throttled time updates for server-side watch-position persistence, ms).
Default: 5000.
Set to 0 to disable.
Use progress instead of time to avoid per-frame callback noise when saving resume positions.
Misc
expose
expose?: boolean
Attach the player instance to window.player for browser-console debugging. Default false.
When true, the nmplayer factory function is additionally attached to window.nmplayer.
Both are cleaned up on dispose().
baseUrl
baseUrl?: string
Base URL prepended to relative media source URLs.
Separate from baseImageUrl, this covers audio/video sources, not artwork.
disableControls / disableMediaControls
disableControls?: boolean
disableMediaControls?: boolean
disableControls disables all keyboard shortcuts; disableMediaControls disables only the media-key bindings. Neither affects the native <video controls> attribute — use the controls option for that.
Playlist item fields
Track kinds
Sidecar files attach through the typed top-level fields on a playlist item (subtitles, chapters, fonts, previewSpriteUrl). A v1-style unified tracks array is not a field on VideoPlaylistItem. The kind on each entry tells the player what the file is:
| Kind | Purpose |
|---|---|
subtitles | WebVTT or ASS/SSA subtitle file. Set ext: 'ass' for ASS files. |
chapters | WebVTT chapter markers. Parsed into the chapters() list on the player. |
thumbnails | WebVTT sprite manifest for seek-preview thumbnails. Its cues point at the sprite image; this file maps to previewSpriteUrl. |
fonts | Font files required by ASS subtitles for the OctopusPlugin renderer. |
Use the top-level item fields subtitles, chapters, fonts, and previewSpriteUrl directly:
{
id: 'sintel',
title: 'Sintel',
description: 'A girl named Sintel searches for a baby dragon she calls Scales.',
url: 'https://protected.cdn.your-domain.com/Sintel.(2010)/Sintel.(2010).NoMercy.m3u8',
image: '/w780/q2bVM5z90tCGbmXYtq2J38T5hSX.jpg',
duration: 894,
subtitles: [
{ id: 'eng', kind: 'subtitles', label: 'English', language: 'eng', url: '/Sintel.(2010)/subtitles/Sintel.(2010).NoMercy.eng.full.vtt', default: true },
{ id: 'dut', kind: 'subtitles', label: 'Dutch', language: 'dut', url: '/Sintel.(2010)/subtitles/Sintel.(2010).NoMercy.dut.full.vtt' },
],
chapters: [
{ index: 0, start: 0, end: 120, title: 'Opening' },
{ index: 1, start: 120, end: 480, title: 'The Search' },
{ index: 2, start: 480, end: 894, title: 'Finale' },
],
fonts: [
{ file: '/Sintel.(2010)/fonts/fonts.json' },
],
previewSpriteUrl: '/Sintel.(2010)/thumbs_256x109.vtt',
}
A v1-style tracks[] array is not accepted on VideoPlaylistItem directly. To migrate v1 items, run them through normalizeVideoItem() from @nomercy-entertainment/nomercy-video-player/compat, which maps tracks[] entries onto the typed fields: kind: 'subtitles' (or 'text', or no kind) into subtitles, kind: 'thumbnails' into previewSpriteUrl, and kind: 'fonts' into fonts. A legacy kind: 'chapters' file is also promoted to previewSpriteUrl when no thumbnails entry is present.
Series episode labels
For TV content, the desktop-ui top bar derives a season/episode label from the numeric season and episode fields, so you don’t format it into the title yourself.
The primary line shows show (or title for a standalone movie); the secondary line shows the episode label followed by the episode title (title, when it differs from show):
{
id: 'bbb-ep3',
title: 'The Big Escape',
show: 'Big Buck Bunny',
season: 1,
episode: 3,
url: '/Big.Buck.Bunny.(2008)/Big.Buck.Bunny.(2008).NoMercy.m3u8',
image: '/w780/xtdybjRRZ15mCrPOvEld305myys.jpg',
duration: 596,
}
// Top bar primary: "Big Buck Bunny"
// Top bar secondary: "S1E3 • The Big Escape"
The label format follows the season number: S{season}E{episode} for a normal season, Extras E{episode} when season is 0, and A{episode} when no season is set.
These labels are built in code from the numeric fields and are not localized.
Resume playback
Set progress on a playlist item to give plugins and UI components the information they need to render a watched-percentage bar in the queue panel.
The player reads progress.percentage for display; consumers own persistence.
{
id: 'tears-of-steel',
title: 'Tears of Steel',
description: 'A group of warriors fight to reclaim humanity from a robotic army.',
url: '/Tears.of.Steel.(2012)/Tears.of.Steel.(2012).NoMercy.m3u8',
image: '/w780/bsy1GCxTNKqZ3vPn4U7sDG7SIAL.jpg',
duration: 734,
progress: {
timestamp: 1748734800000, // Unix epoch milliseconds
percentage: 62, // 0–100, consumer-calculated
},
}
timestamp is a Unix epoch millisecond value; the player exposes it raw.
Format it for display in your UI plugin (e.g. new Date(timestamp).toLocaleDateString()).
Storage
The player persists plugin state (volume, subtitle selection, quality preference) through a pluggable storage interface.
IStorage
interface IStorage {
get(key: string): string | null | Promise<string | null>;
set(key: string, value: string): void | Promise<void>;
remove(key: string): void | Promise<void>;
getJSON<T>(key: string): T | null | Promise<T | null>;
setJSON<T>(key: string, value: T): void | Promise<void>;
}
Backends can be synchronous or async — await is used throughout, so both work.
Built-in backends
| Class | Behaviour |
|---|---|
LocalStorageBackend | Default. Synchronous. Silently falls back to in-memory when localStorage is blocked. |
IndexedDBBackend | Async. Suitable for large values where the localStorage quota is too small. |
MemoryStorageBackend | In-memory only. Use in SSR, tests, or any environment without persistent storage. |
Example: IndexedDB backend
import { nmplayer } from '@nomercy-entertainment/nomercy-video-player';
import { IndexedDBBackend } from '@nomercy-entertainment/nomercy-player-core';
player.setup({
storage: new IndexedDBBackend({ dbName: 'my-app', storeName: 'player-prefs' }),
playlist: [/* ... */],
});
IndexedDBBackend accepts optional dbName, storeName, and version constructor options.
The database opens lazily on first access, so construction has no overhead.
Example: Server-synced storage
Replace storage with any object satisfying IStorage to persist preferences server-side:
const serverStorage = {
async get(key: string) {
const res = await fetch(`/api/player-prefs/${key}`, {
headers: { Authorization: `Bearer ${token}` },
});
return res.ok ? res.text() : null;
},
async set(key: string, value: string) {
await fetch(`/api/player-prefs/${key}`, {
method: 'PUT',
body: value,
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'text/plain',
},
});
},
async remove(key: string) {
await fetch(`/api/player-prefs/${key}`, {
method: 'DELETE',
headers: { Authorization: `Bearer ${token}` },
});
},
async getJSON<T>(key: string): Promise<T | null> {
const raw = await this.get(key);
if (raw === null) return null;
try { return JSON.parse(raw) as T; } catch { return null; }
},
async setJSON<T>(key: string, value: T) {
await this.set(key, JSON.stringify(value));
},
};
player.setup({
storage: serverStorage,
playlist: [/* ... */],
});
Note: Each plugin receives an auto-namespaced wrapper around the storage backend, so keys from different plugins never collide with each other or with player-level keys.