Troubleshooting
No audio plays
Symptom: play() resolves, play event fires, but no sound.
Causes and fixes:
-
Browser autoplay policy. The browser blocks audio until the user has interacted with the page. Call
play()only inside a user gesture (click, keydown). -
Volume is 0. Check
player.volume(). Defaults to 100, but a stored preference may have overridden it. -
Muted. Check
player.volumeState(). Callplayer.unmute()if needed. -
Audio output routed to wrong device. Call
player.audioOutputs()and verify the target device is listed. Useplayer.audioOutput(deviceId)to route to a specific device, orplayer.selectAudioOutput()to open the system picker. -
Source URL unreachable. Open the URL directly in the browser. If it 404s or 401s, the track
urlfield is wrong or auth is missing.
crossfadeTo() does nothing
Symptom: crossfadeTo() call appears to succeed but no audible transition occurs.
Causes and fixes:
-
AudioElementBackendselected. The default backend uses arequestAnimationFrameloop for crossfade (~50 fps). It is less accurate thanWebAudioBackend. Crossfade is functional but may be imperceptible on short durations. Switch withawait player.backend('webaudio')for sample-accurate fades. -
Duration too short. The default
crossfadeDefaults.durationis 5 seconds. If your track is shorter than the fade duration, the crossfade completes instantly. ReducedurationinCrossfadeOptions. -
Already transitioning.
isTransitioning()returnstrue. Wait for thecrossfadeCompleteevent before starting another. -
AutoAdvancePluginfired first. If track end and your manualcrossfadeTo()call overlap, the plugin may have already advanced. Usecrossfade: trueandcrossfadeDurationinAutoAdvancePluginoptions instead of callingcrossfadeTo()manually fromended.
Lyrics are not displayed
Symptom: LyricsPlugin is registered but no lyrics appear.
Causes and fixes:
-
No
lyricsUrlon the track.LyricsPluginreadsgetLyricsUrl(item)if you set that option, otherwiseitem.lyricsUrl. If neither is set, no fetch occurs. Check yourMusicPlaylistItem.lyricsUrl. -
No custom resolver. Without a
getLyricsUrloption onLyricsPlugin, it usesitem.lyricsUrldirectly. This still works if the URL is correct. -
Unsupported file extension. Parser selection is by URL extension.
.lrcand.vttare built-in. Custom formats needplayer.registerCueParser(myParser). -
CORS blocked. The fetch must succeed from the browser. Add
Access-Control-Allow-Originheaders on the lyric host or proxy through your own server. -
loadedevent not fired. Listen toplayer.on('plugin:lyrics:loaded', ...). If it never fires, check the network tab for a failed fetch on the lyrics URL.
Lyrics are out of sync
Symptom: Lyrics display at the wrong time.
Causes and fixes:
-
LRC timestamp format. Verify your
.lrcfile uses[MM:SS.xx]format. Non-standard formats (e.g. seconds-only) will not parse correctly. -
Seek position not updating lyrics. If the user seeks,
LyricsPluginre-scans from the cue list. This is automatic. If it is not working, ensure you are listening tolineEnternot pollingcurrent(). -
Clock drift with
WebAudioBackend. TheWebAudioBackendusesAudioContext.currentTimewhich is sample-accurate but may diverge fromDate.now()after long sessions. This is expected. Useplayer.time()notDate.now()for any sync you build.
backend('webaudio') throws
Symptom: await player.backend('webaudio') throws an error.
Causes and fixes:
-
Safari and iOS.
AudioContextrequires a user gesture on iOS. Callbackend('webaudio')only inside a click or touch handler. -
Context suspended. The
AudioContextmay be suspended after a page visibility change. Listen tovisibilitychangeand callaudioContext?.resume()on page show. -
Already using the requested backend. Not an error, but verify with
player.backend().kindbefore switching.
Phase stuck in 'loading'
Symptom: player.phase() returns 'loading' indefinitely.
Causes and fixes:
-
Source URL returns a non-media content-type. HLS streams must return
application/vnd.apple.mpegurl. MP3/FLAC must return an audio content-type. A redirect to a login page returnstext/html, which hangs the parser. -
Auth token expired and
refreshOnUnauthenticatedis not set. A 401 with no refresh config produces a stale request. Provideauth.refreshOnUnauthenticatedin your setup config. -
HLS manifest parse error. Enable debug logging to capture the raw error.
Debug logging
Enable verbose output:
const player = nmMPlayer('main').setup({
playlist: [],
logLevel: 'debug',
});
With logLevel: 'debug', the player logs every event, state transition, plugin lifecycle call, and network request to the browser console.
Filter by [nmMPlayer] to isolate player logs.
Checking player state programmatically
console.log({
phase: player.phase(),
playState: player.playState(),
volume: player.volume(),
volumeState: player.volumeState(),
backend: player.backend().kind,
transitioning: player.isTransitioning(),
currentTrack: player.item()?.name,
queue: player.queue().length,
});
Paste this into the browser console at any time to snapshot player state.
See also
- FAQ, common questions
- Configuration, all setup options
- Events, event reference for logging
- Backends, capability comparison