Skip to content

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

TypeScript
playlist?: T[] | string

Initial queue. Pass an array of VideoPlaylistItem-compatible objects, or a URL to fetch a JSON playlist.

TypeScript
// 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 playlistResolvingplaylistReady (or playlistError). The player always reaches ready, so if the fetch fails, it starts with an empty queue.

autoPlay

TypeScript
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

TypeScript
muted?: boolean

Start muted. Default false.

preload

TypeScript
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

TypeScript
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

TypeScript
controls?: boolean

Show the browser’s native video controls. Default false. Do not combine with DesktopUiPlugin, the built-in UI replaces native controls.

stretching

TypeScript
stretching?: 'uniform' | 'fill' | 'exactfit' | 'none'

How the video fills its container. Default 'uniform' (letterbox/pillarbox, preserving aspect ratio).

ValueBehavior
'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

TypeScript
theaterDefault?: boolean

Start in theater mode. Default false.

Playback rates

playbackRates

TypeScript
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

TypeScript
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

TypeScript
defaultAudioLanguage?: string

BCP-47 language tag. Auto-selects the audio track matching this language. Falls back to the first available track.

defaultQuality

TypeScript
defaultQuality?: 'auto' | number

Initial quality level. 'auto' enables hls.js ABR. A number sets a fixed level index. Default: 'auto'.

Image URLs

baseImageUrl

TypeScript
baseImageUrl?: string

Base URL prepended to relative image, poster, and thumbnail paths on playlist items. Useful for TMDB-style paths:

TypeScript
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

TypeScript
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

TypeScript
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

TypeScript
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

TypeScript
crossfadeLeadSeconds?: number

How many seconds before the outgoing item ends the player begins the crossfade window. Only used when crossfadeEnabled: true. Default 3.

crossfadeTailSeconds

TypeScript
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

TypeScript
storage?: IStorage

Plugin state persistence backend. Defaults to LocalStorageBackend. See Core: Adapters for alternatives.

Logging

logLevel

TypeScript
logLevel?: 'silent' | 'error' | 'warn' | 'info' | 'debug' | 'trace'

Log verbosity. Default: 'info'. Set to 'debug' to see plugin-level events.

logger

TypeScript
logger?: ILogger

Custom logger. Defaults to the player core’s console logger. See Core: Adapters for alternatives.

debug

TypeScript
debug?: boolean

Deprecated alias for logLevel: 'debug'. Use logLevel instead.

Platform

platform

TypeScript
platform?: IPlatform

Platform abstraction bundle. Defaults to browserPlatform. See Core: Adapters for alternatives. Override individual sub-ports for Capacitor, Tauri, or Electron:

TypeScript
import { browserPlatform } from '@nomercy-entertainment/nomercy-player-core';

player.setup({
platform: { ...browserPlatform, wakeLock: myNativeWakeLock },
});

i18n

language

TypeScript
language?: string

Initial language (BCP-47). Default: navigator.language.

translations

TypeScript
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:

TypeScript
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

TypeScript
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.

TypeScript
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

TypeScript
metricsIntervalMs?: number

Interval for the playback:metrics event (ms). Default: 10000. Set to 0 to disable.

progressIntervalMs

TypeScript
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

TypeScript
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

TypeScript
baseUrl?: string

Base URL prepended to relative media source URLs. Separate from baseImageUrl, this covers audio/video sources, not artwork.

disableControls / disableMediaControls

TypeScript
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:

KindPurpose
subtitlesWebVTT or ASS/SSA subtitle file. Set ext: 'ass' for ASS files.
chaptersWebVTT chapter markers. Parsed into the chapters() list on the player.
thumbnailsWebVTT sprite manifest for seek-preview thumbnails. Its cues point at the sprite image; this file maps to previewSpriteUrl.
fontsFont files required by ASS subtitles for the OctopusPlugin renderer.

Use the top-level item fields subtitles, chapters, fonts, and previewSpriteUrl directly:

TypeScript
{
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):

TypeScript
{
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.

TypeScript
{
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

TypeScript
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

ClassBehaviour
LocalStorageBackendDefault. Synchronous. Silently falls back to in-memory when localStorage is blocked.
IndexedDBBackendAsync. Suitable for large values where the localStorage quota is too small.
MemoryStorageBackendIn-memory only. Use in SSR, tests, or any environment without persistent storage.

Example: IndexedDB backend

TypeScript
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:

TypeScript
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.