Headless contract substrate for the NoMercy player ecosystem. 28 adapter ports. Pluggable everything.
  • TypeScript 99.3%
  • JavaScript 0.7%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-08-04 19:20:00 +02:00
.github/workflows chore(release): promote player-core to stable v2.0.0 2026-07-18 02:43:29 +02:00
scripts feat(core): API unification consolidation — v2 RC (2.0.0-rc.19) 2026-06-30 03:34:35 +02:00
src fix(auth): ask the consumer per url instead of stamping every media request 2026-08-04 02:37:18 +02:00
.gitattributes feat(core): API unification consolidation — v2 RC (2.0.0-rc.19) 2026-06-30 03:34:35 +02:00
.gitignore feat(events): time event carries the full TimeState snapshot 2026-07-05 00:48:39 +02:00
CHANGELOG.md chore(release): 2.0.3 2026-07-31 01:47:59 +02:00
CLAUDE.md feat(player): runtime sidecar subtitle injection (addSubtitleTrack/removeSubtitleTrack) 2026-07-09 15:41:42 +02:00
CODE_OF_CONDUCT.md feat(core): API unification consolidation — v2 RC (2.0.0-rc.19) 2026-06-30 03:34:35 +02:00
CONTRIBUTING.md feat(core): API unification consolidation — v2 RC (2.0.0-rc.19) 2026-06-30 03:34:35 +02:00
eslint.config.js chore(lint): consume published eslint-plugin-player; fix plugin boundary violations 2026-07-05 05:44:45 +02:00
LICENSE feat(core): API unification consolidation — v2 RC (2.0.0-rc.19) 2026-06-30 03:34:35 +02:00
MIGRATION.md docs(migration): correct subpath table to match real exports 2026-07-10 06:04:35 +02:00
package-lock.json chore(release): 2.0.4 2026-08-04 19:20:00 +02:00
package.json chore(release): 2.0.4 2026-08-04 19:20:00 +02:00
README.md docs(core): fix stale fetch docstrings and README doc links 2026-07-04 21:50:00 +02:00
RELEASING.md chore(core): unship embedded eslint-plugin; refresh 2.0.0 migration and release docs 2026-07-04 03:48:28 +02:00
SECURITY.md feat(core): API unification consolidation — v2 RC (2.0.0-rc.19) 2026-06-30 03:34:35 +02:00
TESTING.md feat(core): API unification consolidation — v2 RC (2.0.0-rc.19) 2026-06-30 03:34:35 +02:00
tsconfig.build.json feat(core): API unification consolidation — v2 RC (2.0.0-rc.19) 2026-06-30 03:34:35 +02:00
tsconfig.json feat(core): API unification consolidation — v2 RC (2.0.0-rc.19) 2026-06-30 03:34:35 +02:00
vitest.config.ts feat(core): API unification consolidation — v2 RC (2.0.0-rc.19) 2026-06-30 03:34:35 +02:00

npm license bundlephobia

Full documentation: https://docs.nomercy.tv/nomercy-player-core/

nomercy-player-core

The shared, headless engine that the video and music players are built on.

It carries everything that is not specific to video or audio: the queue, auth, the plugin system, the typed event bus, i18n, and storage.

You stay in charge.

Nothing renders a UI on its own. Nothing is forced on you.

  • Everything is opt-in. No plugin runs until you addPlugin it. The engine ships quiet, you add only what you want.
  • Swap any behavior through adapters. The clock, fetch, storage, logger, retry policy, shuffle, URL resolver, stream registry, and more are each an interface with a default. Pass your own to setup(). No subclassing.
  • Plain events and methods. The engine reports through a typed event bus. How you react, and what you build, is yours.

You rarely install this package directly.

Pull in nomercy-video-player or nomercy-music-player and the core comes with it.

Install it on its own only when you are writing a plugin or building a new player package on the core.

npm install @nomercy-entertainment/nomercy-player-core

Quick start

The core is the player engine. Compose its method mixins onto a class and you have a working player: the queue, transport, loading pipeline, time and volume, the typed event bus, auth, and the plugin system are all there. A library on top of the core adds only the medium-specific piece, the backend that turns a source into sound or picture.

import {
  composeMixins,
  initPlayerCoreState,
  lifecycleMethods,
  playerCoreMethods,
  queueMethods,
  timeMethods,
  transportMethods,
  volumeMethods,
} from '@nomercy-entertainment/nomercy-player-core';

class MyPlayer {
  constructor(id: string) {
    initPlayerCoreState(this, { className: 'MyPlayer' });
    this.setup({ playlist: [{ id, url: '...' }] });
  }
}

composeMixins(
  MyPlayer.prototype,
  playerCoreMethods,
  lifecycleMethods,
  queueMethods,
  transportMethods,
  timeMethods,
  volumeMethods,
);

const player = new MyPlayer('intro');
player.on('ready', () => player.play());
player.queue();
player.time(30);

That player drives the full core surface. The only thing it does not do on its own is render a medium, which is what nomercy-video-player and nomercy-music-player add. Reach for the core directly when you are building a new player package like those, not when you just want to play video or audio.

Two extension points put you in control of the rest. Adapters replace any cross-cutting concern through setup(), no subclassing. Override only what you want; everything else keeps its default:

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

player.setup({
  storage: new LocalStorageBackend(),   // or your own IStorage
  logger: myLogger,                      // your own ILogger
  shuffleStrategy: myShuffle,            // your own IShuffleStrategy
  urlResolver: mySignedUrls,             // your own IUrlResolver
});

Plugins are opt-in and ride the same typed event bus. Nothing registers itself; you add what you want. A plugin reads through this.on, emits its own events, and needs no manual teardown, the base disposes every this.on subscription and mounted element for it:

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

export class PlayCountPlugin extends Plugin {
  static override readonly id = 'play-count';

  override use(): void {
    this.on('play', () => this.emit('play-count:changed', undefined));
  }
}

player.addPlugin(PlayCountPlugin);

Documentation

The docs site is the full reference, ordered from first player to plugin author:

License

Apache-2.0

Repository: github.com/NoMercy-Entertainment/nomercy-player-core