Skip to content

Chapters

Named chapters in your video, shown on the seek bar and available for programmatic navigation. Chapters are provided per item; the player core’s media-tracks layer reads the inline chapters array (or a sidecar kind: 'chapters' VTT).

Prerequisites: Video Quick Start. For the full chapter API see Chapters.

Inline chapter data

The simplest approach is to embed chapter data directly in each queue item:

TypeScript
const player = nmplayer('main').setup({
baseUrl: 'https://raw.githubusercontent.com/NoMercy-Entertainment/nomercy-media/master/Films',
playlist: [
{
id: 'sintel',
url: '/Sintel.(2010)/Sintel.(2010).NoMercy.m3u8',
chapters: [
{ index: 0, start: 0, end: 90, title: 'Cold Open' },
{ index: 1, start: 90, end: 900, title: 'Act 1' },
{ index: 2, start: 900, end: 1800, title: 'Act 2' },
{ index: 3, start: 1800, end: 2700, title: 'Credits' },
],
},
],
});

DesktopUiPlugin reads chapter data automatically and renders chapter markers on the seek bar. No extra configuration is needed.

VTT chapter file

VideoPlaylistItem has no tracks field — tracks was removed in v2. The player reads chapters from the chapters array on each item. Parse the VTT on your server (or in a loader) and supply the result as ChapterRef[]:

TypeScript
import type { ChapterRef } from '@nomercy-entertainment/nomercy-video-player';

// Parse the VTT on your side and pass the result as ChapterRef[]:
const chapters: ChapterRef[] = [
{ index: 0, start: 0, end: 90, title: 'Cold Open' },
{ index: 1, start: 90, end: 900, title: 'Act 1' },
{ index: 2, start: 2700, end: 2820, title: 'Credits' },
];

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

A valid WebVTT chapters file (for reference — parse server-side before passing to the player):

Code
WEBVTT

cold-open
00:00:00.000 --> 00:01:30.000
Cold Open

act-1
00:01:30.000 --> 00:15:00.000
Act 1

credits
00:45:00.000 --> 00:47:00.000
Credits

Chapter events and API

TypeScript
// Emitted by seekToChapter() / nextChapter() / previousChapter() (not on natural playback boundary crossing):
player.on('chapter', ({ index, title }) => {
console.log(`Now in chapter ${index}: ${title}`);
});

// Read all chapters for the current item:
const chapters = player.chapters();
// [{ index, start, end, title }, ...]

// Read the currently active chapter:
const current = player.chapter();
// { index, start, end, title } | null

Programmatic chapter seek

Jump to a chapter by index or by its start time:

TypeScript
// Seek to the start of chapter at index 2:
const chapters = player.chapters();
if (chapters[2]) {
player.time(chapters[2].start);
}

// Build a chapter menu:
chapters.forEach((chapter) => {
const button = document.createElement('button');
button.textContent = chapter.title;
button.addEventListener('click', () => player.time(chapter.start));
chapterMenu.appendChild(button);
});

Custom chapter source

Load chapters from a database or API instead of a VTT file:

TypeScript
import { Plugin } from '@nomercy-entertainment/nomercy-player-core';
import type { Chapter } from '@nomercy-entertainment/nomercy-player-core';
import type { NMVideoPlayer, VideoPlaylistItem } from '@nomercy-entertainment/nomercy-video-player';

// There is no chapterSource config field — build a Plugin that fetches
// chapters on 'current' and emits 'chapters' so the player and UI pick them up.
class ApiChapterSourcePlugin extends Plugin<NMVideoPlayer<VideoPlaylistItem>> {
static readonly id = 'api-chapter-source';
static readonly version = '1.0.0';

override use(): void {
this.on('current', async ({ item }) => {
if (!item) return;
// Use this.fetch() so the player's auth pipeline applies.
const data = await this.fetch<Array<{ id: number; start_ms: number; end_ms: number; name: string }>>(
`/api/chapters/${item.id}`,
{ responseType: 'json' },
);
const chapters: Chapter[] = data.map((ch, index) => ({
index,
start: ch.start_ms / 1000,
end: ch.end_ms / 1000,
title: ch.name,
}));
this.player.emit('chapters', { chapters });
});
}
}

player.addPlugin(ApiChapterSourcePlugin);
  • Chapters: full IChapterSource interface and Chapter type reference
  • Desktop UI: chapter markers on the seek bar are automatic with DesktopUiPlugin
  • Recipes: Subtitles: combining chapter markers with subtitle tracks