Skip to content

Octopus (ASS/SSA)

OctopusPlugin is the plugin to reach for when your media library includes anime or any content with styled .ass or .ssa subtitle tracks. It wraps @nomercy-entertainment/nomercy-subtitle-octopus (the NoMercy fork of libass-wasm) and handles the lifecycle, font loading, and auth-gating so the WASM worker never touches the network directly. For plain .vtt subtitle tracks, use SubtitleOverlayPlugin instead.

@nomercy-entertainment/nomercy-subtitle-octopus is a plain dependency bundled with the package. The plugin imports it dynamically at runtime as a safety net. When the import fails the plugin logs a warning and marks itself as degraded; playback continues normally and ASS/SSA subtitles will not render.

TypeScript
import { OctopusPlugin } from '@nomercy-entertainment/nomercy-video-player/plugins';
import type { OctopusOptions } from '@nomercy-entertainment/nomercy-video-player/plugins';

Plugin id: 'octopus'

What it does

The plugin listens to the player’s 'subtitle' event. When a track is selected it resolves the URL by calling player.subtitles?.() and indexing into that list by the zero-based index the event carries. If the resolved extension is not .ass or .ssa the renderer is torn down and the track is left to the native text-track pipeline or SubtitleOverlayPlugin. For ASS/SSA files it pre-fetches the subtitle body via this.fetch (the player core’s auth pipeline), resolves fonts into blob URLs the same way, and instantiates the libass WASM renderer.

On the 'current' event the renderer is torn down and the per-item font cache is cleared so the new item starts clean.

All network I/O goes through the player core auth pipeline. The WASM worker receives pre-fetched content as blob URLs and inline strings, it never performs authenticated XHR itself.

Options

OptionTypeDefaultDescription
workerUrlstringbundled pathURL for the modern WASM worker script.
legacyWorkerUrlstringnoneURL for the legacy non-WASM worker, for browsers without WebAssembly.
fallbackFontstringnoneFont URL used when a requested font is not in the item’s fonts list.
fontsstring[][]Static font URLs preloaded for every item, supplementing per-item fonts.
targetFpsnumberlibrary defaultRenderer target FPS. Leave unset to let the library choose.
renderMode'wasm-blend' | 'js-blend' | 'lossy''wasm-blend'Render quality vs. performance trade-off.
lazyFileLoadingbooleanfalseFetch subtitle chunks lazily, useful for very large ASS files.
prescaleFactornumberlibrary defaultInternal scaler ratio. Leave unset for default scaling.
renderAheadnumber10Frames pre-computed ahead of currentTime. Higher values smooth playback through heavy ASS effects at the cost of memory.
debugbooleanfalseEnable debug logging in the libass WASM worker.

Events

Events fire on the plugin channel. Subscribe from outside the plugin using the string form.

EventPayloadDescription
renderer:ready{ url: string }Fired after the libass renderer initialises and the first frame is ready for the given subtitle URL.
TypeScript
player.on('plugin:octopus:renderer:ready', ({ url }) => {
console.log('libass renderer ready for', url);
});

Methods

subtitle

TypeScript
plugin.subtitle(): string | null
plugin.subtitle(url: string | null): Promise<void>

Read or set the active subtitle URL directly, bypassing the player core’s track list. Use this for ASS files the consumer supplies outside the subtitles array.

TypeScript
import { OctopusPlugin } from '@nomercy-entertainment/nomercy-video-player/plugins';

const plugin = player.getPlugin(OctopusPlugin);

await plugin?.subtitle(
'https://raw.githubusercontent.com/NoMercy-Entertainment/nomercy-media/master/Anime/Rail.Wars%21.(2014)/Rail.Wars%21.S00E00/subtitles/Rail.Wars%21.(2014).S00E00.NoMercy.eng.full.ass',
);

await plugin?.subtitle(null); // clear

fonts

TypeScript
plugin.fonts(): readonly string[]
plugin.fonts(urls: string[]): Promise<void>

Read or replace the plugin-level static font list.

The getter returns blob: URLs when fonts have already been fetched and loaded for the active item, or the original plugin-level URL strings before the first load. Calling it with new URLs clears the per-item font cache and re-loads the active subtitle so the new fonts apply immediately.

renderer

TypeScript
plugin.renderer(): object | null

Returns the raw libass renderer handle for advanced consumers. The returned value is an opaque object; its type is not part of the public API. The plugin retains lifecycle ownership, do not call dispose() on the returned instance directly.

Font resolution

Fonts are resolved in this order for each item:

  1. The fonts array on the active VideoPlaylistItem. Each entry is a FontTrackRef object ({ file: string; label?: string }) whose file property is a fonts.json manifest URL or a direct font file URL.
  2. OctopusOptions.fonts, static URLs registered at plugin level, applied to all items.
  3. OctopusOptions.fallbackFont, a single URL used when ASS requests a font not found above.

All font fetches go through the player core’s auth pipeline. Each binary is converted to a blob URL and passed to the WASM worker. Blob URLs are revoked when the renderer is torn down to avoid memory leaks.

Registration

TypeScript
import nmplayer from '@nomercy-entertainment/nomercy-video-player';
import { OctopusPlugin } from '@nomercy-entertainment/nomercy-video-player/plugins';

const player = nmplayer('player')
.addPlugin(OctopusPlugin, {
workerUrl: '/subtitles-octopus-worker.js',
fallbackFont: '/fonts/NotoSans-Regular.ttf',
renderAhead: 10,
})
.setup({
baseUrl: 'https://raw.githubusercontent.com/NoMercy-Entertainment/nomercy-media/master/Anime',
playlist: [
{
id: 'rail-wars-s00e00',
title: 'Rail Wars! S00E00',
url: '/Rail.Wars!.(2014)/Rail.Wars!.S00E00/Rail.Wars!.(2014).S00E00.NoMercy.m3u8',
subtitles: [
{
id: '1',
kind: 'subtitles',
label: 'English (ASS)',
language: 'en',
url: '/Rail.Wars!.(2014)/Rail.Wars!.S00E00/subtitles/Rail.Wars!.(2014).S00E00.NoMercy.eng.full.ass',
},
],
fonts: [
{
file: '/Rail.Wars!.(2014)/Rail.Wars!.S00E00/fonts.json',
},
],
},
],
});

See also