Skip to main content

Text tracks & captions

Selecting tracks​

player.getTextTracks(); // TextTrack[] (subtitle / caption tracks)
player.setTextTrack(id, 'showing'); // 'disabled' | 'hidden' | 'showing'
player.on('texttrackchange', ({ track }) => console.log(track.language));

The subtitles/CC control-bar button opens a native Video.js menu with the available languages plus "captions off".

Side-loaded WebVTT​

SourceDescription.textTracks attaches WebVTT tracks that are not embedded in the HLS/DASH manifest (subtitles, or a kind: 'chapters' cue file):

player.setSource({
sources: [{ src: 'https://example.com/video.m3u8', type: 'application/x-mpegURL' }],
textTracks: [
{ kind: 'chapters', src: 'https://example.com/chapters.vtt', srclang: 'en' },
{ kind: 'subtitles', src: 'https://example.com/subs.it.vtt', srclang: 'it', label: 'Italiano' },
],
});

Side-loaded tracks are cleared automatically on the next setSource(): no manual cleanup when switching sources.

Validation​

textTracks is checked before anything reaches Video.js. It must be an array, and every entry must be an object with a non-empty src and a kind from the TextTrackKind union (subtitles, captions, descriptions, chapters, or metadata). Entries that fail (a bare URL string passed instead of an array, a missing or unrecognised kind, a missing src) are dropped with a console.warn and ignored. This matters because Video.js silently rewrites an unknown kind to subtitles, which would otherwise show a phantom language entry in the CC menu for a track that never loads.

The caption-settings dialog​

By default the subs/CC menu also carries a "captions settings" entry that opens Video.js's text-track styling dialog (font, colour, background, edge style, opacity). The wrapper re-skins that dialog to match the EVE settings flyout (translucent dark card, flat controls, accent-filled Done, ghost Reset) and lays its six field groups out as aligned rows.

To remove the entry and the dialog entirely, pass captionSettings: false:

new EvePlayer(container, { captionSettings: false });

Under the hood this passes textTrackSettings: false to the Video.js constructor, which drops the textTrackSettings component. Video.js only adds the menu entry when that component exists, so it disappears with it.

Chapters​

Chapter cues are read from a kind: 'chapters' text track on the current source (embedded in the manifest or side-loaded above). They are kept separate from getTextTracks() / setTextTrack() on purpose: chapters are not a language choice, and mixing them in would leak a non-language entry into any subtitle picker built on top.

player.getChapterCues(); // ChapterCue[] (all cues from the 'chapters' track)
player.on('chapterentercue', ({ cue }) => console.log(cue.text));

Whenever the current source has a chapters track with cues, Video.js's chapters control-bar button (the list icon) shows up on its own, next to the audio and subtitles menus. Its popup is hoisted to the player's bottom-right corner and skinned to match those menus (click-only, translucent dark panel). Clicking a row seeks to that chapter; the row for the chapter currently playing carries the accent checkmark.

Pass chapters: false to the constructor to remove the button even when a chapters track is present; getChapterCues() and the chapterentercue event are unaffected.

new EvePlayer(container, { chapters: false });

Chapters on the timeline​

The same cues also mark up the progress bar. It is split into one segment per chapter, with a small gap at each boundary, and while the mouse hovers the bar the title of the chapter under it shows right above the time. When the source also has thumbnails, the thumbnail sits above the title.

This is on by default (chapterMarkers: true) and skipped on live streams. It is independent of the chapters button, so either can be turned off alone:

// Chapters menu, but a plain progress bar:
new EvePlayer(container, { chapterMarkers: false });

The gap width is the --eve-chapter-gap CSS variable (default 2px, see Theming).