Skip to content

Core: Typed Errors

Every failure the kit or a plugin raises is a PlayerError, never a bare thrown string, so a catch block can branch on a type instead of parsing a message.

The hierarchy

PlayerError is the base: a stable code (core:auth/forbidden, fillz:viz/canvas-init-failed), a severity ('fatal' | 'error' | 'warning' | 'info'), a discriminated scope ({ kind: 'core' }, { kind: 'backend'; id }, { kind: 'plugin'; id }, and the rest), an optional cause, context, and a user-facing suggestion. Subclasses narrow the failure category: AuthError (extends NetworkError, thrown on 401/403), DrmError, MediaFormatError, NetworkError, ResourceError, StateError (wrong lifecycle phase), StreamError, PluginError, and BrowserPolicyError (a browser-enforced restriction like a blocked autoplay policy, or an unsupported browser API such as RemotePlayback or the audio-output picker). isHttp(century) checks context.httpStatus against a 1xx-5xx range without a manual range comparison at every call site.

Construct one with a factory rather than the raw constructor where the kit provides one: stateError(code, message, context?), mediaFormatError(...), resourceError(...), pluginError(...), browserPolicyError(...). Each stamps the right scope and a sensible default severity for you.

Severity tiers and the error event

Four severities map to four event channels: fatal (unrecoverable, the player is shutting down), error (recoverable, e.g. a sidecar failed to load), warning and info (observability only). Every one delivers a PlayerErrorEvent: the underlying error, its severity and scope, and DOM-style propagation control (markHandled(), stopImmediatePropagation(), preventDefault() to suppress a kit-side default action like auto-retry).

Retry policy

DEFAULT_RETRY_POLICY is an exported preset table mapping error codes to a RetryConfig (attempts, backoff, baseMs, maxMs, refreshFirst), keyed by precedence from most to least specific: exact code, then category prefix, then an HTTP range ('4xx' / '5xx'), then a '*' wildcard. Neither the kit’s own authFetch pipeline nor Plugin.fetch consults it automatically, Plugin.fetch() defaults to { attempts: 0 } (no retry) unless you pass a RetryConfig explicitly. In the table itself a core:auth/forbidden entry never retries, a core:network/timeout entry retries five times with exponential backoff. Pass an entry from this map, or your own RetryConfig, as the per-call retry option on Plugin.fetch() to apply it at one call site.

TypeScript
/**
* Every kit and plugin error is a typed `PlayerError`, never a raw thrown
* string. Subclasses (`AuthError`, `DrmError`, `MediaFormatError`,
* `NetworkError`, `StateError`, `StreamError`, ...) narrow the `code` /
* `severity` / `scope` contract so a `catch` block can branch on `instanceof`
* instead of parsing message text. `DEFAULT_RETRY_POLICY` is a reference table of
* per-code retry shapes; pass an entry as the per-call `retry` option on
* `Plugin.fetch` (the fetch pipeline never consults it automatically, defaulting
* to `{ attempts: 0 }`).
*/

import type { PlayerError } from '@nomercy-entertainment/nomercy-player-core';
import {
AuthError,
DEFAULT_RETRY_POLICY,
NetworkError,
PlayerError as PlayerErrorClass,
StateError,
stateError,
} from '@nomercy-entertainment/nomercy-player-core';

const notReady = stateError('core:player/not-ready', 'Player has not been setup() yet.');
console.log(notReady instanceof PlayerErrorClass); // true
console.log(notReady instanceof StateError); // true
console.log(notReady.code); // 'core:player/not-ready'
console.log(notReady.severity); // 'error'

// AuthError extends NetworkError, so a generic network handler still catches it.
function handle(error: PlayerError): void {
if (error instanceof AuthError) {
console.log('auth problem:', error.code);
} else if (error instanceof NetworkError) {
console.log('network problem:', error.code);
} else {
console.log('other error:', error.code, error.severity);
}
}

handle(notReady); // 'other error: core:player/not-ready error'

// Retry policy lookup — forbidden never retries, a timeout retries with backoff.
console.log(DEFAULT_RETRY_POLICY['core:auth/forbidden']); // { attempts: 0 }
console.log(DEFAULT_RETRY_POLICY['core:network/timeout']); // { attempts: 5, backoff: 'exponential', ... }

Next steps