Crossfade and Gapless
Smooth music transitions: a volume-faded crossfade or a preloaded gapless handoff. This applies to the music player only.
Prerequisites: Quick Start. For the full crossfade API see Crossfade.
Crossfade vs gapless, when to choose
| Feature | Crossfade | Gapless |
|---|---|---|
| Overlap | Both tracks play simultaneously | Tracks play sequentially |
| Volume | Outgoing fades down, incoming fades up | Constant |
| Use case | Album transitions, radio, DJ-style mixes | Continuous music (prog rock, live albums) |
| Config | crossfadeDefaults.duration + AutoAdvancePlugin | Omit crossfade; AutoAdvancePlugin preloads next |
Enabling crossfade
import nmMPlayer from '@nomercy-entertainment/nomercy-music-player';
import { AutoAdvancePlugin } from '@nomercy-entertainment/nomercy-music-player/plugins';
const player = nmMPlayer('main')
.addPlugin(AutoAdvancePlugin, {
crossfade: true, // hand off to crossfadeTo on trackEndingSoon
crossfadeDuration: 5, // seconds
})
.setup({
baseUrl: 'https://raw.githubusercontent.com/NoMercy-Entertainment/nomercy-media/master/Music',
crossfadeDefaults: {
duration: 5, // seconds
curve: 'equal-power',
},
trackEndingSoonThreshold: 8, // fire trackEndingSoon with 8s remaining
playlist: [
{
id: 'kjc-01',
name: 'Thaw You Out',
url: '/D/Derek%20Clegg/%5B2010%5D%20KJC/01%20Thaw%20You%20Out.mp3',
artist: 'Derek Clegg',
},
{
id: 'kjc-02',
name: 'Hope',
url: '/D/Derek%20Clegg/%5B2010%5D%20KJC/02%20Hope.mp3',
artist: 'Derek Clegg',
},
],
});
player.on('ready', () => {
player.item(0, { autoplay: true });
});
AutoAdvancePlugin with crossfade: true calls player.crossfadeTo(next, { duration: crossfadeDuration }) when trackEndingSoon fires.
Set crossfadeDuration to match or be less than trackEndingSoonThreshold.
Crossfade curve comparison
'equal-power' keeps perceived loudness stable:
equal-power:
outgoing gain: cos(t × π/2)
incoming gain: sin(t × π/2)
sum: constant at 1.0 throughout
linear:
outgoing gain: 1 − t
incoming gain: t
sum: dips to ~0.71 at t = 0.5
Use 'equal-power' for all standard music.
'linear' creates an audible dip at the midpoint, which is almost never what you want.
Manual crossfade trigger
To crossfade on an explicit user action (next button, click on a playlist item):
import type { MusicPlaylistItem } from '@nomercy-entertainment/nomercy-music-player';
async function crossfadeToTrack(nextTrack: MusicPlaylistItem): Promise<void> {
if (player.isTransitioning()) return; // ignore if already fading
await player.crossfadeTo(nextTrack, {
duration: 3,
curve: 'equal-power',
});
}
Nested crossfades are rejected by the player, so crossfadeTo is a no-op when isTransitioning() returns true.
Monitoring crossfade progress
player.on('crossfadeStart', ({ from, to, duration }) => {
// duration is in milliseconds
console.log(`Fading from "${from?.name ?? 'nothing'}" to "${to.name}" over ${duration}ms`);
showTransitionUI();
});
player.on('crossfadeComplete', ({ track }) => {
console.log(`Now playing: ${track.name}`);
hideTransitionUI();
});
// Poll mid-fade:
if (player.isTransitioning()) {
showFadeIndicator();
}
Gapless playback
Gapless preloads the next track so it can begin immediately when the current one ends:
import { AutoAdvancePlugin } from '@nomercy-entertainment/nomercy-music-player/plugins';
const player = nmMPlayer('main')
.addPlugin(AutoAdvancePlugin, {
preloadNextOnEnding: true, // preload next track on trackEndingSoon
crossfade: false, // no volume fade, hard cut
})
.setup({
trackEndingSoonThreshold: 30, // preload 30 s before end
playlist: [track1, track2, track3],
});
AutoAdvancePlugin with preloadNextOnEnding: true calls player.load(next, { slot: 'next' }) when trackEndingSoon fires, so the next track’s audio is buffered before ended.
Backend choice and accuracy
| Backend | Crossfade mechanism | Gapless accuracy |
|---|---|---|
audio-element | RAF loop (~50 fps, ±20 ms) | Browser-dependent. Chrome/Safari good, Firefox may gap |
webaudio | linearRampToValueAtTime (sample-accurate) | Sample-accurate on all browsers with Web Audio |
Switch to webaudio for production DJ-style crossfades:
player.setup({
backend: 'webaudio',
crossfadeDefaults: { duration: 6, curve: 'equal-power' },
});
Timing pitfalls
The crossfade duration (seconds) must be less than or equal to trackEndingSoonThreshold:
// Correct: threshold gives 8s to run a 5s fade:
trackEndingSoonThreshold: 8,
crossfadeDefaults: { duration: 5 }
// Wrong: 6s fade but only 3s warning; fade clips at end:
trackEndingSoonThreshold: 3,
crossfadeDefaults: { duration: 6 }