Errors
The player uses a structured error hierarchy.
All errors carry a string code, a severity, and a human-readable message.
Catching by code or by class is always preferred over catching raw Error.
Error classes
| Class | Code prefix | When |
|---|---|---|
PlayerError | core:* | Base class. General player errors. |
NotImplementedError | core:not-implemented/* | Method exists in the contract but is not implemented by the current backend or library |
NetworkError | core:network/* | HTTP or connectivity failures |
AuthError | core:auth/* | Authentication and authorization failures |
MediaFormatError | core:media/* | Unsupported or malformed media format |
StreamError | core:stream/* | HLS/DASH stream errors |
ResourceError | core:resource/* | Missing or inaccessible resources |
DrmError | core:drm/* | DRM/EME failures |
BrowserPolicyError | core:policy/* | Browser autoplay or permissions policy blocks |
PluginError | plugin:* | Errors originating inside a plugin |
All classes are exported from @nomercy-entertainment/nomercy-player-core@beta.
Error codes
Code format: <vendor>:<category>/<detail>.
Core codes always start with core:. Plugin codes are namespaced with the plugin id, e.g. equalizer:band/out-of-range.
Auth errors (AuthError)
| Code | Status | When |
|---|---|---|
core:auth/forbidden | 403 | Access denied. Never retried. |
core:auth/unauthenticated | 401 | Token missing or expired and refresh failed. |
core:auth/refresh-failed | 401 | refreshOnUnauthenticated threw or returned no token. |
Network errors (NetworkError)
| Code | When |
|---|---|
core:network/timeout | Request exceeded timeoutMs. |
core:network/request-timeout | Server returned 408. |
core:network/aborted | Request was aborted (e.g. on plugin dispose). |
core:network/offline | navigator.onLine is false when a request is made. |
core:network/not-found | 404. |
core:network/gone | 410. |
core:network/rate-limited | 429. |
core:network/client-error | Other 4xx. |
core:network/server-error | 500. |
core:network/bad-gateway | 502. |
core:network/service-unavailable | 503. |
core:network/gateway-timeout | 504. |
core:network/server-error-other | Other 5xx. |
core:network/parse-failed | Response body could not be parsed. |
Stream errors (StreamError)
| Code | When |
|---|---|
core:stream/fragment-failed | An HLS fragment/segment failed to load or parse. |
core:stream/hls-attach-failed | hls.js could not attach to the media element. |
core:stream/no-factory-match | No registered stream factory matched the source. |
Media format errors (MediaFormatError)
| Code | When |
|---|---|
core:media/codec-unsupported | Browser rejects the codec string via canPlayType. |
core:media/hls-unsupported | HLS source but no native or hls.js support. |
core:media/load-failed | The media element failed to load the source. |
core:media/missing-url | Playlist item has no resolvable url. |
Resource errors (ResourceError)
| Code | When |
|---|---|
core:resource/worker-init-failed | A Web Worker could not be started (e.g. the OctopusPlugin worker). |
Browser policy errors (BrowserPolicyError)
| Code | When |
|---|---|
core:policy/autoplay-blocked | Browser blocked play() due to autoplay policy. |
core:policy/fullscreenUnsupported | Fullscreen requested but not available. |
core:policy/pipUnsupported | Picture-in-picture requested but not available. |
core:policy/emeUnsupported | EME/DRM requested but not available. |
core:policy/castUnavailable | Cast requested but the Cast SDK is unavailable. |
There is a core:policy/<capability>Unsupported (or Unavailable) code for each gated browser capability — AirPlay, Web Audio, canvas 2D, capture-stream, IndexedDB, remote playback, wake lock, setSinkId, and the audio-output picker among them.
Playlist errors (PlayerError)
| Code | When |
|---|---|
core:playlist/fetch-error | URL-based playlist fetch failed. |
core:playlist/parse-error | Response is not a valid JSON array. |
Plugin errors (PluginError)
| Code | When |
|---|---|
core:plugin/missing-dep | A required plugin dependency is not registered. |
core:plugin/version-mismatch | A dependency plugin version is below minVersion. |
core:plugin/duplicate-id | A plugin with the same id is already registered (and replaces is not set). |
core:plugin/init-timeout | use() did not resolve within pluginInitTimeoutMs. |
Not-implemented errors (NotImplementedError)
| Code | When |
|---|---|
core:not-implemented/subtitles | subtitles() called on NMMusicPlayer. |
core:not-implemented/<feature> | Any structural API stub not supported by the current backend. |
Severity tiers
| Severity | Meaning |
|---|---|
'fatal' | Playback cannot continue. Player stops and emits fatal. Plugin marks itself failed. |
'error' | Operation failed. Plugin keeps running. error event fires. |
'warning' | Degraded behavior. Plugin survives and emits warning. |
'info' | Observability, no impact on behavior. |
Listening to errors
player.on('error', (event) => {
console.error(`Error ${event.error.code}: ${event.error.message}`);
});
player.on('fatal', () => {
showUserMessage('Playback failed. Please reload.');
});
player.on('warning', (event) => {
console.warn(`Warning ${event.error.code}: ${event.error.message}`);
});
The handler receives a PlayerErrorEvent. The structured error lives on .error (so it is event.error.code, not event.code); the rest of the payload is the severity, scope, timestamp, and the cancellable-event controls:
interface PlayerErrorEvent {
error: PlayerError; // .code, .message, .cause?, .context?, .suggestion?
severity: 'fatal' | 'error' | 'warning' | 'info';
scope: ErrorScope;
timestamp: number;
markHandled(): void;
isHandled(): boolean;
stopImmediatePropagation(): void;
isPropagationStopped(): boolean;
preventDefault(): void; // suppress the kit's default action (e.g. auto-retry)
isDefaultPrevented(): boolean;
}
NotImplementedError
NotImplementedError is thrown when a method exists in the public contract but the current backend or library does not support it.
Known cases in v2.0:
player.subtitles()onNMMusicPlayer: audio backends have no subtitle tracksGroupListeningPlugin,DrmPlugin,LiveTranscodingPluginonNMMusicPlayer: these throwNotImplementedErrorin theiruse()(planned for v2.1)
Catch by class, not by message:
import { NotImplementedError } from '@nomercy-entertainment/nomercy-player-core';
try {
const tracks = player.subtitles();
} catch (error) {
if (error instanceof NotImplementedError) {
// expected: audio player doesn't have subtitle tracks
} else {
throw error; // re-throw unexpected errors
}
}
Code always follows core:not-implemented/<feature>.
Static recovery policy
Plugins declare how they respond to specific error codes:
class MyPlugin extends Plugin {
static readonly onError: Record<string, PluginRecoveryAction> = {
'core:stream/segment-load-failed': 'retry-once',
'core:auth/forbidden': 'disable',
'core:network/timeout': 'ignore',
};
}
| Action | Behavior |
|---|---|
'retry-once' | Retry the failing operation once |
'fallback' | Activate the plugin’s fallback behavior (plugin must implement activateFallback()) |
'disable' | Disable the plugin gracefully |
'ignore' | Log and continue, no action taken |
Recovery actions are declared per plugin through static onError; 'retry-once' calls the plugin’s retryLastOperation() and 'fallback' calls its activateFallback(). There is no setup-level override.
Inside plugins: this.throw and this.report
Never throw raw errors or emit error events directly from a plugin. Always use the plugin’s methods:
// Fatal: plugin fails. Player and other plugins continue.
this.throw({
severity: 'fatal',
code: 'nomercy:sync/connection-lost',
message: 'Lost connection to sync server',
});
// Non-fatal: operation fails. Plugin keeps running.
this.throw({
severity: 'error',
code: 'nomercy:sync/fetch-failed',
message: 'Sync fetch returned an error',
});
// Warning / info: observability. No action needed.
this.report({
severity: 'warning',
code: 'nomercy:sync/rate-limited',
message: 'Sync endpoint rate-limited, will retry in 30s',
});
Errors thrown with this.throw are caught, wrapped in PluginError, emitted via the player’s event bus, and if severity is 'fatal', the plugin is marked failed.
Other plugins always continue.