Skip to content

Lyrics and Equalizer

Building a Now Playing screen with synced lyrics and an equalizer. Both are opt-in, so include only what you need.

Prerequisites: Quick Start. Full references: LyricsPlugin, Equalizer.

Lyrics setup

TypeScript
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',
cover: '/D/Derek%20Clegg/%5B2010%5D%20KJC/01%20Thaw%20You%20Out.jpg',
lyricsUrl: '/D/Derek%20Clegg/%5B2010%5D%20KJC/01%20Thaw%20You%20Out.lrc', // per-track LRC sidecar
artist: 'Derek Clegg',
},
],
});

player.on('ready', () => {
player.item(0, { autoplay: true });
});

LyricsPlugin reads lyricsUrl from each track on the current event, fetches the file, parses it through the player core’s cue parser registry (the LRC parser is pre-registered), and attaches a CueTracker that fires timed events as playback progresses.

Displaying synced lyrics

TypeScript
import { LyricsPlugin } from '@nomercy-entertainment/nomercy-music-player/plugins';

const lyricsEl = document.getElementById('lyrics')!;

// Rebuild the display when a new track's lyrics load:
player.on('plugin:lyrics:loaded', () => {
const lyricsPlugin = player.getPlugin(LyricsPlugin)!;
const allCues = lyricsPlugin.all();

lyricsEl.innerHTML = '';
allCues.forEach((cue, index) => {
const line = document.createElement('p');
line.dataset.index = String(index);
line.textContent = cue.payload.text;
lyricsEl.appendChild(line);
});
});

// Highlight the active line on every cue enter:
player.on('plugin:lyrics:lineEnter', ({ text }) => {
lyricsEl.querySelector('.active')?.classList.remove('active');

// Find the line element that matches the active text:
const all = lyricsEl.querySelectorAll<HTMLElement>('p');
for (const el of all) {
if (el.textContent === text) {
el.classList.add('active');
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
break;
}
}
});

// Dim on line exit:
player.on('plugin:lyrics:lineExit', () => {
lyricsEl.querySelector('.active')?.classList.remove('active');
});

Custom lyrics URL resolver

When lyricsUrl on the track item is not available, for example when loading lyrics from a server API by track ID, pass a getLyricsUrl resolver to addPlugin:

TypeScript
import { LyricsPlugin } from '@nomercy-entertainment/nomercy-music-player/plugins';

player.addPlugin(LyricsPlugin, {
getLyricsUrl: (track) => {
// Derive the LRC URL from the track's API path or ID:
return `https://api.example.com/lyrics/${track.id}.lrc`;
},
});

The getLyricsUrl function receives the full MusicPlaylistItem and returns a URL string or undefined (no lyrics for this track).

Manual lyrics fetch

For explicit control, loading lyrics on demand rather than automatically:

TypeScript
const lyricsPlugin = player.getPlugin(LyricsPlugin)!;

// Disable auto-fetch:
player.addPlugin(LyricsPlugin, { autoFetch: false });

// Load manually:
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`);
}

LRC format

The player core’s LRC parser handles the standard timestamped format:

Code
[00:12.00] First line of lyrics
[00:17.20] Second line
[00:22.40] Third line

No extra configuration is needed. Parser selection is driven by the URL file extension: .lrc files use the LRC parser; .vtt files use the WebVTT parser. To add a custom parser for another format, register it with player.registerCueParser(myParser).

Equalizer setup

EqualizerPlugin requires AudioGraphPlugin. Register AudioGraphPlugin first.

TypeScript
import { AudioGraphPlugin, EqualizerPlugin } from '@nomercy-entertainment/nomercy-music-player/plugins';

player
.addPlugin(AudioGraphPlugin)
.addPlugin(EqualizerPlugin);

19 built-in presets are included. To customize, supply presets in options or call eq.addCustomPreset() after setup.

Building an EQ UI

TypeScript
import { EqualizerPlugin } from '@nomercy-entertainment/nomercy-music-player/plugins';

const eq = player.getPlugin(EqualizerPlugin)!;
const eqContainer = document.getElementById('eq')!;

const allBands = eq.bands().filter((band) => band.frequency !== 'Pre');

allBands.forEach((band) => {
const slider = document.createElement('input');
slider.type = 'range';
slider.min = String(eq.bandSliderMin(band.frequency));
slider.max = String(eq.bandSliderMax(band.frequency));
slider.step = String(eq.bandSliderStep(band.frequency));
slider.value = String(band.gain);
slider.title = `${band.frequency} Hz`;

slider.addEventListener('input', () => {
eq.band(band.frequency as number, parseFloat(slider.value));
});

eqContainer.appendChild(slider);
});

// Sync slider positions when a band changes:
player.on('plugin:equalizer:band:changed', ({ band }) => {
const sliders = eqContainer.querySelectorAll<HTMLInputElement>('input');
for (const slider of sliders) {
if (slider.title === `${band.frequency} Hz`) {
slider.value = String(band.gain);
break;
}
}
});

Applying presets

TypeScript
import { EqualizerPlugin } from '@nomercy-entertainment/nomercy-music-player/plugins';

const eq = player.getPlugin(EqualizerPlugin)!;

// Apply a built-in preset by name:
eq.preset('Rock');

// Read current preset name:
const name = eq.preset();

// Reset to initial state:
eq.reset();

EQ state is automatically persisted to the configured storage backend when persistKey is set. On the next nmMPlayer('main').setup(), the EQ restores its previous state.

Combining lyrics and EQ

TypeScript
import { LyricsPlugin, AutoAdvancePlugin, MediaSessionPlugin, AudioGraphPlugin, EqualizerPlugin } from '@nomercy-entertainment/nomercy-music-player/plugins';

const player = nmMPlayer('main')
.addPlugin(AutoAdvancePlugin)
.addPlugin(MediaSessionPlugin)
.addPlugin(LyricsPlugin)
.addPlugin(AudioGraphPlugin)
.addPlugin(EqualizerPlugin)
.setup({ playlist: myTracks });

player.on('ready', () => {
player.item(0, { autoplay: true });
});

// Lyrics panel:
player.on('plugin:lyrics:lineEnter', ({ text }) => {
lyricsDisplay.textContent = text;
});

// EQ panel (preset applied by UI):
player.on('plugin:equalizer:band:changed', ({ band }) => {
updateEqSlider(band.frequency, band.gain);
});

During a crossfade only the outgoing track is routed through the EQ graph; the incoming track plays un-equalized until it becomes primary at the end of the fade.

See also