Skip to content

What is the NoMercy Video Player

nomercy-video-player is the headless web video player that powers video on NoMercy TV. It plays standard video through the browser’s native <video> element and adds adaptive streaming over HLS, and because the playback backend is a swappable adapter, other streaming formats plug in the same way. It also handles the genuinely hard parts for you (matching HDR to the display, rendering subtitles in several formats, switching bitrate as the network shifts on an adaptive stream) and then gets out of your way.

The important word is headless. There is no UI in the box and no layout you have to accept. What you get is a working video pipeline wired to plain events, plain methods, and a set of building blocks you can swap. Everything you see on screen is yours to build, or you bring in the optional DesktopUiPlugin and start from a finished one. The choice stays with you.

An empty shell you drive

Think of the player as an empty shell with three ways in:

  • Methods are how you tell it what to do: play(), pause(), volume(50), time(t) to seek, next().
  • Events are how it tells you what happened: time, play, pause, current, level-switched, and the rest of a fully typed event map.
  • Adapters are the fundamental behaviors you can exchange: the playback backend, the thumbnail, chapter, and subtitle sources, storage, the event bus, the clock, and the URL resolver. Each one has a sensible default and a port you can replace without subclassing anything.

Nothing is hidden behind a UI you cannot reach. If the player can do it, there is a method, an event, or an adapter for it.

What it does to your page

You give the player an element. It mounts into that element in place, it does not wrap it or replace it.

HTML
<div id="player" style="width:100%;aspect-ratio:16/9"></div>
TypeScript
import nmplayer from '@nomercy-entertainment/nomercy-video-player';

const player = nmplayer('player').setup({
baseUrl: 'https://raw.githubusercontent.com/NoMercy-Entertainment/nomercy-media/master/Films',
playlist: [
{ title: 'Sintel', url: '/Sintel.(2010)/Sintel.(2010).NoMercy.m3u8' },
],
});

After setup(), your own element is the player container. The player adds the nomercyplayer class (your existing classes and styles stay), appends a <video> child, and from then on keeps the element in sync with playback:

HTML
<div id="player" class="nomercyplayer paused">
<video>…</video>
<!-- any plugin UI, like DesktopUiPlugin, mounts here too -->
</div>

State classes you can use or ignore

As playback changes, the player reflects state as CSS classes on your container. You never have to read them, but they are there the moment you want to style around state without writing a single event listener.

The play state is one of playing, paused, stopped, ended, loading, or buffering (mutually exclusive). On top of that it toggles muted, fullscreen, pip, and theater, and it flips active / inactive as the viewer moves the pointer or goes idle.

This is the complete set. Every class below is added and removed for you by the core as events fire, so this table is the one place you need:

ClassOn the container whileGroup
playingmedia is playingplay state (one at a time)
pausedplayback is paused, and as the initial resting stateplay state
stoppedplayback has been stoppedplay state
endedplayback reached the endplay state
loadinga source is loadingplay state
bufferingplayback stalled waiting for dataplay state
mutedaudio is mutedindependent toggle
fullscreenthe player is in fullscreenindependent toggle
pipthe player is in picture-in-pictureindependent toggle
theatertheater mode is onindependent toggle
activethe viewer moved the pointer recentlyactivity (paired)
inactivethe viewer has gone idleactivity (paired)

The six play-state classes are mutually exclusive: the container always carries exactly one. The four toggles are independent and can stack. active and inactive are a pair, exactly one at a time.

CSS
/* Your container, your CSS. Dim the picture while paused: */
.nomercyplayer.paused video { opacity: 0.6; }

/* Reveal your own controls only while the viewer is active: */
.nomercyplayer .my-controls { opacity: 0; transition: opacity 150ms; }
.nomercyplayer.active .my-controls { opacity: 1; }

Use them to drive an entire UI from CSS, mix them with event listeners, or ignore them completely and style nothing. They are an affordance, never a requirement.

Driven by events and methods

Because the player is event and API driven, your UI is just a listener on one side and a caller on the other. This is the whole pattern:

TypeScript
player.on('time', ({ time }) => {
// move your scrubber, update your clock
});
player.on('pause', () => {
// show your big play button
});

player.play();
player.volume(80);

There is no template to fight and no markup you have to match. React to what you care about, drive what you need, and leave the rest.

Swappable building blocks

The defaults are good, but none of them are locked in. Each fundamental behavior is an adapter port you can replace with your own:

  • Swap the backend to drive a different media element or a non-HLS source.
  • Swap the thumbnail, chapter, or subtitle source to read your own formats.
  • Swap storage to persist preferences server-side instead of in the browser.
  • Swap the event bus, clock, or URL resolver when you need to integrate with an existing system or test in isolation.

That is the door this design opens: a working engine you can reshape from the outside, so the player bends to your product instead of your product bending to the player.

Where it sits

The generic parts (queue, auth, plugins, i18n, storage) live in nomercy-player-core underneath, so this package stays focused on the hard video parts. Plugin authors type against IVideoPlayer<T> rather than the concrete class.

3
Your applicationYour app code

Your UI and app logic, built on the player.

2
nomercy-video-playerYou are here

Video-specific methods and events (IVideoPlayer<T>).

1
nomercy-player-coreDependency

Queue, auth, plugins, event bus, i18n, storage.

Start here