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
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
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:
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:
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:
[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.
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
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
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
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
- LyricsPlugin:
getLyricsUrl,autoFetch,fetchLyrics(), all events - Equalizer: full
EqualizerPluginmethod and event reference - Crossfade and Gapless: EQ applies through crossfade transitions