Every player instance emits events from BaseEventMap.
Subscribe with player.on(name, handler), unsubscribe with player.off(name, handler), subscribe once with player.once(name, handler).
Library-specific maps (MusicEventMap, VideoEventMap) extend BaseEventMap with domain-only events and may narrow payload types for shared events.
Ordered sequence: beforeSetup → setupStart → configResolved → pluginsRegistering → pluginsRegistered → streamsReady → authReady → playlistResolving → playlistReady → mediaReady → ready.
| Event | Payload | When |
|---|
beforeSetup | void | Before any setup work begins |
setupStart | { container: HTMLElement } | Setup phase entered |
configResolved | { config: BasePlayerConfig } | Options normalised |
pluginsRegistering | void | Plugin registration loop begins |
pluginsRegistered | void | All plugins’ use() settled |
streamsReady | void | Stream factories registered |
authReady | void | Auth config resolved |
playlistResolving | { url: string } | Remote playlist URL fetch began |
playlistReady | { length: number } | Playlist loaded (or empty on failure) |
playlistError | { url, error, code } | Remote playlist fetch or parse failed |
mediaReady | void | First item loaded into backend |
ready | void | Player is fully ready |
Error events (each stage emits <stage>Error: PlayerErrorEvent on failure):
setupStartError, configResolvedError, pluginsRegisteringError, pluginsRegisteredError, streamsReadyError, authReadyError, playlistResolveError, mediaReadyError
Every before* event is cancellable (preventDefault()), delayable (delay(promise)), and stops propagation on request (stopImmediatePropagation()).
| Event | Payload | When |
|---|
beforePlay | BeforeEvent<ActionOptions> | Before play() executes |
playing | void | Backend confirms media is actively rendering (after buffering resolves) |
firstFrame | void | First video/audio frame presented |
play | ActionOptions | Play dispatched; phase entering starting |
playPrevented | { reason, cause? } | beforePlay was prevented |
beforePause | BeforeEvent<ActionOptions> | Before pause() executes |
pause | ActionOptions | Paused |
pausePrevented | { reason, cause? } | beforePause was prevented |
beforeStop | BeforeEvent<ActionOptions> | Before stop() executes |
stop | ActionOptions | Stopped |
stopPrevented | { reason, cause? } | beforeStop was prevented |
beforeNext | BeforeEvent<ActionOptions> | Before next() executes |
next | ActionOptions | Advanced to next item |
nextPrevented | { reason, cause? } | beforeNext was prevented |
beforePrevious | BeforeEvent<ActionOptions> | Before previous() executes |
previous | ActionOptions | Went back to previous item |
previousPrevented | { reason, cause? } | beforePrevious was prevented |
beforeLoad | BeforeEvent<{ item, source? }> | Before load(item) executes |
loadPrevented | { reason, cause? } | beforeLoad was prevented |
ended | void | Current item ended naturally |
playing fires when the backend confirms media is rendering, equivalent to the HTML playing event.
It fires after buffering resolves, not just on the play() call.
Wired by the per-library backend.
| Event | Payload | When |
|---|
beforeSeek | BeforeEvent<{ time, source? }> | Before any seek executes |
seek | { time, source? } | Seek dispatched; backend receiving new position |
seeked | { time } | Seek settled, backend has confirmed the new position |
seekPrevented | { reason, cause? } | beforeSeek was prevented |
seek fires at dispatch time.
seeked fires after the backend repositions.
| Event | Payload | When |
|---|
beforeMutation | BeforeEvent<{ method, args, phase, dispatchStack }> | Before a mutating method executes (when guards are active) |
mutationPrevented | { method, reason, cause? } | beforeMutation was prevented |
| Event | Payload | When |
|---|
phase | { from: PlayerPhase; to: PlayerPhase } | Player moves between lifecycle phases |
| Event | Payload | When |
|---|
time | { time: number } | Every animation frame during playback |
duration | { duration: number } | Backend resolves the total duration |
progress | { time, duration, percentage } | Throttled, at most every progressIntervalMs (default 5 s) |
Use progress instead of time for server-side watch-position saves.
time fires on every frame.
| Event | Payload | When |
|---|
volume | { level: number } | Volume changed (0–100) |
mute | { muted: boolean } | Mute toggled |
repeat | { state: 'off' | 'all' | 'one' } | Repeat mode changed |
shuffle | { state: 'off' | 'on' } | Shuffle mode changed |
| Event | Payload | When |
|---|
queue | BasePlaylistItem[] | Queue replaced |
queue:append | { items, from: number } | Items appended |
queue:prepend | { items } | Items prepended |
queue:insert | { items, index } | Items inserted |
queue:remove | { id, index, item } | Item removed |
queue:move | { from, to } | Item moved |
queue:clear | { previousLength } | Queue cleared |
queue:shuffle | void | Queue randomly reordered |
queue:sort | void | Queue sorted |
queue:exhausted | void | next() reached the end with no next item and repeat is 'off' |
current | { item: BasePlaylistItem | undefined; index: number } | Active item cursor moved |
| Event | Payload | When |
|---|
backlog | BasePlaylistItem[] | Backlog replaced |
backlog:append | { items } | Items added to backlog |
backlog:remove | { id, index, item } | Item removed from backlog |
backlog:clear | { previousLength } | Backlog cleared |
| Event | Payload | When |
|---|
subtitle | { track: number | null } | Subtitle track selection changed |
subtitleCue | { cues: SubtitleCue[]; language?: string } | Active subtitle cues changed |
subtitleStyle | SubtitleStyle | Subtitle style written |
audioTrack | { id: number | null } | Audio track selection changed |
chapter | { index, title } | Chapter seeked via seekToChapter |
chapters | { chapters: ReadonlyArray<Chapter> } | Chapter list resolved for active item |
qualityState | { state: 'auto' | 'manual' } | Quality selection mode changed |
audioTrackState | { state: 'default' | 'manual' } | Audio track selection mode changed |
level-switched | { level: number } | HLS adaptive level switched |
| Event | Payload | When |
|---|
backend:changed | { kind: string } | Active backend swapped |
backend:loading | { url, kind } | Backend started loading |
backend:loaded | { url, kind, duration } | Backend finished loading |
backend:error | { error, kind } | Backend encountered an error |
backend:stalled | { time } | Playback stalled |
backend:ratechange | { rate } | Playback rate changed |
backend:waiting | void | Backend is waiting for data |
| Event | Payload | When |
|---|
stream:manifest-loaded | { url } | HLS manifest loaded |
stream:level-switched | { level, label } | Stream variant switched |
stream:fragment-loaded | { url, durationMs } | Segment fetched |
stream:level-considered | { candidate, decided, reason } | ABR candidate evaluated |
stream:error | { details, fatal } | Stream-level error |
stream:encrypted | { initData, initDataType } | Encrypted stream detected |
| Event | Payload | When |
|---|
network:online | void | Network came online |
network:offline | void | Network went offline |
network:slow | { rttMs: number | undefined } | Online but downlink < 1.5 Mbps. Fires only on transition from not-slow to slow, not on every heartbeat. rttMs is undefined on Firefox and Safari (no Network Information API). |
visibility:visible | void | Tab became visible |
visibility:hidden | void | Tab was hidden |
| Event | Payload | When |
|---|
auth:refreshed | { tokenAcquiredAt: number } | Token refreshed successfully |
auth:failed | { error } | Token refresh failed |
| Event | Payload | When |
|---|
castState | { state: CastState } | Cast session state changed |
| Event | Payload | When |
|---|
preloadStart | { item, assets } | Prefetch began for next item |
preloadProgress | { item, loaded, total } | Individual asset fetched |
preloadComplete | { item } | All preload assets fetched |
preloadError | { item, error } | One or more assets failed (non-fatal) |
transitionStart | { outgoing, incoming } | Transition window began |
transitionProgress | { outgoing, incoming, fraction } | Per-frame progress [0..1] |
transitionComplete | { from, to } | Transition done |
transitionCancelled | { reason } | Transition aborted |
| Event | Payload | When |
|---|
cue:enter | CueEventPayload | Playback entered a cue’s time range |
cue:exit | CueEventPayload | Playback exited a cue’s time range |
| Event | Payload | When |
|---|
plugin:installed | { id, version } | Plugin registered successfully |
plugin:enabled | { id } | Plugin enabled |
plugin:disabled | { id, reason? } | Plugin disabled |
plugin:opts:changed | { id, opts } | Plugin options updated |
plugin:disposed | { id } | Plugin removed |
plugin:failed | { id, error } | Plugin use() threw or timed out |
plugin:error | PlayerErrorEvent | Plugin emitted an error |
plugin:warning | PlayerErrorEvent | Plugin emitted a warning |
| Event | Payload | When |
|---|
fatal | PlayerErrorEvent | Unrecoverable error, player shutting down |
error | PlayerErrorEvent | Recoverable error |
warning | PlayerErrorEvent | Observability only |
info | PlayerErrorEvent | Informational |
| Event | Payload | When |
|---|
playback:metrics | PlaybackMetrics | Periodic metrics snapshot (every metricsIntervalMs, default 10 s) |
fetch:start | { url, pluginId? } | Authenticated fetch began |
fetch:retry | { url, attempt, reason, delayMs, pluginId? } | Fetch retrying after 401 or network error |
fetch:complete | { url, ok, status?, durationMs, pluginId? } | Fetch completed |
activity | { active: boolean } | User activity state changed (pointer/touch/key) |
listeners-changed | { name, count } | Listener count for a named event changed |
dispose | void | Player disposed |