LyricsPlugin
You need synced lyrics.
A track object already has a lyricsUrl field, but something still needs to fetch the file, pick the right parser, and translate raw cue timestamps into events your UI can respond to at exactly the right moment.
That is what this plugin does.
On every current event it resolves the lyric URL, fetches the file through the core’s auth-aware fetch, runs the content through the cue parser registry, and hands the result to a CueTracker that fires line, lineEnter, and lineExit events in sync with playback.
import nmMPlayer from '@nomercy-entertainment/nomercy-music-player';
import { LyricsPlugin } from '@nomercy-entertainment/nomercy-music-player/plugins';
import type { LyricsEvents, LyricsOptions } from '@nomercy-entertainment/nomercy-music-player/plugins';
import type { MusicPlaylistItem } from '@nomercy-entertainment/nomercy-music-player';
Plugin id: 'lyrics'
What it does
The plugin hooks into current and runs automatically when autoFetch is enabled (the default).
A CueTracker wakes at the right timestamp and emits lineEnter and lineExit on the plugin channel, which the player re-emits as plugin:lyrics:lineEnter and plugin:lyrics:lineExit.
The line event fires alongside lineEnter as a convenience alias for single-line karaoke-style displays.
The URL file extension decides which parser runs.
.lrc and .vtt parsers are pre-registered by the core.
For other formats call player.registerCueParser(myParser) before setup().
Options
| Option | Type | Default | Description |
|---|---|---|---|
getLyricsUrl | (track: MusicPlaylistItem) => string | undefined | undefined | Custom URL resolver. When provided it takes precedence over track.lyricsUrl. Use it when lyrics are stored under a URL pattern derived from the track id or when you need to sign the URL. |
autoFetch | boolean | true | Automatically fetch and attach lyrics on every current event. Set to false when you want manual control via fetchLyrics(url). |
Events
Listen from outside the plugin using the namespaced string form on player.on().
| Event | Payload | Description |
|---|---|---|
plugin:lyrics:loaded | { count: number } | Fires at the end of attach() once the cue list is fetched, parsed, and the tracker is attached. count is the number of loaded cues. |
plugin:lyrics:line | { text: string; [key: string]: unknown } | Fires on every line enter. Convenience alias for lineEnter. Use this for single-line karaoke displays. |
plugin:lyrics:lineEnter | { text: string; [key: string]: unknown } | Fires when a cue becomes active. Mirrors the CueTracker enter event. |
plugin:lyrics:lineExit | { text: string; [key: string]: unknown } | Fires when a cue goes inactive. Use to dim or remove the highlight from the previous line. |
player.on('plugin:lyrics:lineEnter', ({ text }) => {
highlightLine(text);
});
player.on('plugin:lyrics:lineExit', ({ text }) => {
dimLine(text);
});
Inside a plugin body you can use the class form: this.on(LyricsPlugin, 'lineEnter', ...).
That form is protected and is not available on an external player.on() call.
Methods
current()
current(): { text: string; [key: string]: unknown } | undefined
Returns the currently active line payload, or undefined when no line is active (between cues or before lyrics are loaded).
const lyricsPlugin = player.getPlugin(LyricsPlugin)!;
const activeLine = lyricsPlugin.current();
if (activeLine) {
console.log(activeLine.text);
}
all()
all(): ReadonlyArray<Cue<{ text: string; [key: string]: unknown }>>
All cues for the current track. Returns an empty array if no lyrics are loaded. Use this to render a full lyric sheet before playback reaches each line.
import type { Cue } from '@nomercy-entertainment/nomercy-player-core';
const lyricsPlugin = player.getPlugin(LyricsPlugin)!;
const cues = lyricsPlugin.all();
cues.forEach((cue) => {
console.log(`[${cue.start}s] ${cue.payload.text}`);
});
clear()
clear(): void
Tears down the active cue tracker and clears all lyric state without disposing the plugin.
The plugin keeps listening for the next current event, so the next track auto-fetches as usual when autoFetch is true.
fetchLyrics(url)
fetchLyrics(url: string): Promise<CueList<{ text: string; [key: string]: unknown }> | undefined>
Explicitly fetch and attach lyrics from a URL.
Calling this takes precedence over the auto-fetch from track.lyricsUrl.
Returns the parsed cue list, or undefined if the fetch or parse failed.
On failure the plugin surfaces the error via player.on('warning', ...) with code 'plugin:lyrics/fetch-failed' (network error) or 'plugin:lyrics/no-parser' (no parser registered for that file extension).
import type { CueList } from '@nomercy-entertainment/nomercy-player-core';
const lyricsPlugin = player.getPlugin(LyricsPlugin)!;
const cueList = await lyricsPlugin.fetchLyrics(
'https://raw.githubusercontent.com/NoMercy-Entertainment/nomercy-media/master/Music/D/Derek%20Clegg/%5B2010%5D%20KJC/01%20Thaw%20You%20Out.lrc',
);
if (cueList) {
console.log(`Loaded ${cueList.cues.length} cues`);
}
Registration
Minimal registration, relying on track.lyricsUrl for the URL:
import nmMPlayer from '@nomercy-entertainment/nomercy-music-player';
import { LyricsPlugin } from '@nomercy-entertainment/nomercy-music-player/plugins';
const player = nmMPlayer('main')
.addPlugin(LyricsPlugin)
.setup({
baseUrl: 'https://raw.githubusercontent.com/NoMercy-Entertainment/nomercy-media/master/Music',
playlist: [
{
id: 'kjc-01',
name: 'Thaw You Out',
url: '/D/Derek%20Clegg/%5B2010%5D%20KJC/01%20Thaw%20You%20Out.mp3',
lyricsUrl: '/D/Derek%20Clegg/%5B2010%5D%20KJC/01%20Thaw%20You%20Out.lrc',
artist: 'Derek Clegg',
},
],
});
With a custom URL resolver when your lyric files live at a pattern the track object does not capture on its own:
import nmMPlayer from '@nomercy-entertainment/nomercy-music-player';
import { LyricsPlugin } from '@nomercy-entertainment/nomercy-music-player/plugins';
import type { MusicPlaylistItem } from '@nomercy-entertainment/nomercy-music-player';
const player = nmMPlayer('main')
.addPlugin(LyricsPlugin, {
getLyricsUrl: (track: MusicPlaylistItem) =>
`https://api.example.com/lyrics/${track.id}.lrc`,
})
.setup({ playlist: myTracks });
Full example with a lyric sheet and active-line highlight:
import nmMPlayer from '@nomercy-entertainment/nomercy-music-player';
import { AutoAdvancePlugin, LyricsPlugin } from '@nomercy-entertainment/nomercy-music-player/plugins';
const player = nmMPlayer('main')
.addPlugin(AutoAdvancePlugin)
.addPlugin(LyricsPlugin, { autoFetch: true })
.setup({
baseUrl: 'https://raw.githubusercontent.com/NoMercy-Entertainment/nomercy-media/master/Music',
playlist: [
{
id: 'kjc-01',
name: 'Thaw You Out',
url: '/D/Derek%20Clegg/%5B2010%5D%20KJC/01%20Thaw%20You%20Out.mp3',
lyricsUrl: '/D/Derek%20Clegg/%5B2010%5D%20KJC/01%20Thaw%20You%20Out.lrc',
artist: 'Derek Clegg',
},
],
});
const lyricsEl = document.getElementById('lyrics')!;
// Build the full lyric sheet as soon as the first line fires:
player.on('plugin:lyrics:lineEnter', () => {
if (lyricsEl.children.length === 0) {
const lyricsPlugin = player.getPlugin(LyricsPlugin)!;
const cues = lyricsPlugin.all();
lyricsEl.innerHTML = cues
.map((cue, index) => `<p data-index="${index}">${cue.payload.text}</p>`)
.join('');
}
});
// Scroll and highlight the active line:
player.on('plugin:lyrics:lineEnter', ({ text }) => {
lyricsEl.querySelector('.active')?.classList.remove('active');
const lineEls = lyricsEl.querySelectorAll<HTMLElement>('p');
for (const lineEl of lineEls) {
if (lineEl.textContent === text) {
lineEl.classList.add('active');
lineEl.scrollIntoView({ behavior: 'smooth', block: 'center' });
break;
}
}
});
player.on('ready', () => {
player.item(0, { autoplay: true });
});
See also
- MusicPlaylistItem, the
lyricsUrlfield on the track object - Recipes: Lyrics and Equalizer, full Now Playing screen walkthrough
- Advanced: Lyrics Sync Deep Dive, CueTracker internals and custom cue parsers