SubtitleOverlayPlugin renders the player’s WebVTT subtitle cue stream as a positioned DOM overlay above the video element. Register it whenever you serve VTT subtitle tracks. For ASS/SSA files use OctopusPlugin instead, this plugin handles VTT only.
Plugin id
'subtitle-overlay'
Import
import { SubtitleOverlayPlugin } from '@nomercy-entertainment/nomercy-video-player/plugins';
What it does
SubtitleOverlayPlugin renders the player’s subtitleCue event stream as a positioned DOM overlay above the video element. It is a pure consumer, all subtitle parsing, VTT fetching, and cue tracking live in the core. The overlay subscribes to the single 'subtitleCue' event and renders one .subtitle-area per active cue.
Features:
- One
.subtitle-areabox per active cue, pooled and reused, no DOM churn - WebVTT cue layout:
line,position,size, andalignattributes from the VTT cue are applied as CSS positioning - Collision detection, when two cues would overlap, the later one is displaced along the line axis
- Overlay is letterbox-fit to the actual video display rectangle (not the container), so cue font size scales with the visible picture, not the player shell
subtitleStyle()settings (font family, size, color, edge style, background) are applied and update live on'subtitleStyle'events- Language tag is mirrored onto
.subtitle-text[data-language]for CSS targeting
For ASS/SSA subtitle files, add the OctopusPlugin instead, this overlay handles VTT only.
Options
SubtitleOverlayPlugin takes no options. All subtitle style settings (font family, size, color, edge style, background) are driven by the player.subtitleStyle() API at runtime; the overlay repaints whenever the subtitleStyle event fires.
Registration
import nmplayer from '@nomercy-entertainment/nomercy-video-player';
import { SubtitleOverlayPlugin } from '@nomercy-entertainment/nomercy-video-player/plugins';
const player = nmplayer('player')
.addPlugin(SubtitleOverlayPlugin)
.setup({
baseUrl: 'https://raw.githubusercontent.com/NoMercy-Entertainment/nomercy-media/master/Films',
playlist: [
{
title: 'Sintel',
url: '/Sintel.(2010)/Sintel.(2010).NoMercy.m3u8',
subtitles: [
{
id: 1,
kind: 'subtitles',
label: 'English',
language: 'en',
url: '/Sintel.(2010)/subtitles/Sintel.(2010).NoMercy.eng.full.vtt',
},
],
},
],
defaultSubtitleLanguage: 'en',
});
DOM structure
The plugin mounts:
| Element | Description |
|---|---|
.nomercyplayer | Player root |
.subtitle-overlay | Letterbox-fit to the actual video display rectangle |
.subtitle-safezone | 5% top/bottom, 3% left/right inset |
.subtitle-area × N | One per active cue, positioned by VTT cue attributes |
.subtitle-text | Styled by subtitleStyle settings |
Subtitle style
The overlay respects all fields of SubtitleStyle via player.subtitleStyle():
player.subtitleStyle({
fontFamily: 'ReithSans, sans-serif',
fontSize: 120, // 120% of default size
textColor: 'white',
textOpacity: 100,
edgeStyle: 'dropShadow',
backgroundColor: 'black',
backgroundOpacity: 80,
});
Changes are applied immediately to all active cue areas without re-rendering.
Track selection API
player.subtitles(); // SubtitleTrack[]
player.subtitle(); // CurrentSubtitleSelection | null ({ index, track }; null = off)
player.subtitle(0); // activate by index
player.subtitle(null); // disable subtitles
player.subtitleState(); // 'on' | 'off'
The subtitle event fires when the active track changes:
player.on('subtitle', ({ track }) => {
// track is the subtitle index, null when subtitles are off
updateSubtitleMenu(track);
});
Subtitle cue stream
The player emits subtitleCue events as active cues change. This is the low-level cue stream useful for custom renderers:
player.on('subtitleCue', ({ cues, language }) => {
renderMyCues(cues);
});
Style persistence
Subtitle style lives in memory via player.subtitleStyle(); the player does not persist it automatically. To remember preferences across reloads, save the style in a subtitleStyle event listener and re-apply it after ready(). The exported StorageBackedSubtitleStyleStore helper (backed by the core IStorage adapter) is one ready-made way to do this; see Recipes: Subtitles.
Accessibility
The overlay is built around two formal standards, plus a few implementation choices that follow from them.
Caption placement (FCC 47 CFR 79.4)
The .subtitle-safezone wrapper insets captions 5% from the top and bottom and 3% from the left and right of the actual video picture, not the player frame.
A ResizeObserver letterbox-fits that safezone to the real video display rectangle and re-fits on fullscreen changes, so the inset always tracks the picture.
Pillarboxed 4:3 content gets 3% of the 4:3 picture, not 3% of the wider 16:9 shell.
This keeps captions clear of player chrome and device bezels so they stay viewable in their entirety, which is what FCC 47 CFR 79.4 requires, and it matches the placement you see on Netflix, YouTube, and Apple TV+.
Resizable text (WCAG 2.1 SC 1.4.4, Level AA)
Caption text is sized with clamp(14px, calc(var(--subtitle-scale, 1) * 2.5cqi), 56px).
The cqi unit scales with the player’s width, so captions grow with the video and follow browser zoom without losing legibility.
The --subtitle-scale value comes from the user’s own font-size preference, so anyone who needs bigger text can get it without leaving the player.
The 14px floor keeps captions readable even on the smallest players, and the 56px ceiling stops them from swallowing the screen.
That satisfies WCAG 2.1 SC 1.4.4 Resize Text: caption text scales well past 200% without losing content.
Right-to-left text
Both the cue area and the cue text carry unicode-bidi: plaintext.
The browser then applies the Unicode Bidirectional Algorithm to each cue’s own content, so Arabic, Hebrew, and mixed-script subtitles render in the correct direction with no per-language setup.
Contrast
The subtitle style system ships five edge effects (drop shadow, uniform outline, depressed, raised, and a stacked shadow) that users combine with their own text and background colours. The stacked-shadow preset layers several black shadows into a strong outline that holds up against whatever video sits behind it. The plugin does not compute a fixed contrast ratio for you, because video backgrounds keep changing, but these controls give users what they need to meet WCAG 1.4.3 on their own content.
Screen readers
The overlay is visual only.
It carries no aria-live region and no role, so assistive technology does not announce the caption text, and screen reader users are expected to follow the audio track itself.
If your app needs on-screen captions announced, add role="status" and aria-live="polite" to the .subtitle-safezone element.