This document freezes the intended Issue 001 contract for 1.0.0-beta.1.
It is not the full implementation spec for browser playback internals. It is the public TypeScript surface and the behavior a developer should build against.
The current implementation target is a browser HTMLAudioElement engine. Non-browser environments may construct the player, but actual playback commands require a browser audio implementation.
- Imperative instance API first
- One active player instance per app/page is the intended
beta.1model - Queue is built into the player instance
- Persistence is opt-in
- One flat source object per item
- Stable caller-provided IDs are required
subscribe(state)is for UI consumption- Typed events are for integrations and side effects
See src/index.ts for the canonical type definitions.
Primary exported types:
AudioSourceAudioPlayerStateAudioPlayerProgressStateAudioPlayerQueueStateAudioPlayerQueuePositionStateAudioPlayerErrorStateAudioPlayerRuntimeErrorAudioPlayerEventMapAudioPlayerPersistedPlayerStateAudioPlayerPersistenceAdapterAudioPlayerPersistenceOptionsAudioPlayerPersistenceControllerAudioPlayerProgressSnapshotAudioPlayerProgressReporterOptionsAudioPlayerProgressReporterControllerAudioPlayerMediaSessionOptionsAudioPlayerMediaSessionControllerAudioPlayerProvideruseAudioPlayeruseAudioPlayerState
- Persistence is opt-in through
attachAudioPlayerPersistence(...) - The first built-in adapter is
createLocalStoragePersistenceAdapter(key) - Restore must not auto-play by itself
- Persistence saves plain-data only
- Persistence should save on pause, ended, source change, queue change, rate/volume changes, and periodically while playing
- Progress reporting is opt-in through
attachAudioPlayerProgressReporter(...) - The library emits normalized progress snapshots and does not own network requests
- Reporting should happen on an interval while playing
- Reporting should also happen on pause, ended, seek completion, and source changes
- Media Session integration is opt-in through
attachAudioPlayerMediaSession(...) - Unsupported browsers should no-op cleanly
- Metadata should come from the current
AudioSource - Play, pause, seek backward, seek forward, previous track, next track, seek to, and stop should be wired when supported
- Playback state and position state should stay in sync while the player changes
- React integration lives at
@yetric/audioplayer/react AudioPlayerProvideris the primary app-level integration- The provider owns a singleton player unless a caller supplies an existing player instance
useAudioPlayer()exposes the underlying imperative playeruseAudioPlayerState()exposes subscribed player state without adding a separate React-owned playback model
- Requires a stable
source.idandsource.src - Replaces the active source
- Does not start playback by itself
- Resets
currentTimeto0 - Transitions to
loading, then toreadywhen metadata becomes available
- If
sourceis provided, the player auto-loads it first - If no source is active, this is an error condition
- Intended to return
Promise<void>because browser playback can be async and reject - If a source is still loading, play intent should be remembered and start once loading completes
- Pauses the active source
- Keeps the current source and playback position intact
- If currently playing, pauses
- Otherwise tries to play the active source
- Pauses and clears the active source without destroying the player instance
- Resets playback progress to
0 - Moves the player to
status: "idle" - Keeps queue items intact but clears the active queue selection
- Clamps to
>= 0 - Clamps to duration when duration is known
beta.1contract should not throw for normal out-of-range values
- Must reject invalid values through the typed error path
- Volume is normalized to
0..1 - Invalid values go through the typed error path
- Replaces the queue
startAtIdselects the active item if providedautoplaystarts playback after selection
- Moves to the next queue item and plays it
- If there is no next item and repeat is off, manual
next()is a no-op - If
repeatMode === "all"and the queue is exhausted, wraps to the first item - If
repeatMode === "one", keeps the current item - Only actual playback end should move the player into
status: "ended"when repeat is off
- If current time is greater than the restart threshold, restarts the current item
- Otherwise moves to the previous queue item
- If there is no previous item and repeat is off, restarts the current item
- Removing the active item from the queue does not forcibly unload the current source
- Clearing the queue removes queue navigation state but does not forcibly unload the current source
- Queue mutations should keep active playback stable unless the caller explicitly loads another item
- Tears down the player instance and listeners
- Caller should treat the instance as unusable after destroy
The exported player state is intended to be plain-data and serializable:
- no DOM nodes
- no functions
- no runtime-only error causes in the state snapshot
- enough derived data that UI consumers do not need to inspect
HTMLAudioElement
idle: no active sourceloading: source change in progressready: source loaded but not currently playingplaying: currently playingpaused: paused with active sourceended: playback finished without silent reseterror: last operation failed
- Queue state is always present, even when empty
queue.position.currentIndex = -1means no active queued itemqueue.itemIdsexists so consumers can compare queue identity without walking full item payloads
progress.currentTime,progress.duration, andprogress.bufferedmirror the flat playback valuesprogress.playedFractionandprogress.bufferedFractionare normalized0..1- UI consumers should not need direct DOM access to render sliders or progress bars
state.erroris intentionally serializable and contains onlycodeandmessage- runtime-only details such as original thrown values stay in the
errorevent payload, not the state snapshot - successful operations clear stale
state.error - unsupported environments should surface a typed
UNSUPPORTED_ENVIRONMENTerror instead of failing silently
state.erroris the user-facing, serializable failure snapshot- the
errorevent payload may include richer runtime causes - failed operations should not corrupt queue structure or silently replace the active source
- successful playback, metadata load, seek completion, rate changes, and volume changes should clear stale errors
The intent is:
- UI uses
subscribe(state) - Integrations use
on(eventName, listener)
Important event families:
sourcechangequeuechangeplay/pauseseeking/seekedtimeupdateratechangevolumechangeendederrordestroy
Explicitly in scope for beta.1:
- browser-first playback
- built-in queue
- repeat modes
- ended-state preservation
- opt-in persistence with
localStorage - opt-in Media Session integration
- React provider integration later
Explicitly not required for Issue 001:
- Media Session implementation
- persistence implementation
- React adapter implementation
- React Native engine abstraction
- The public contract remains
AudioPlayer, not an exported engine interface - The current implementation isolates browser audio behavior behind an internal engine boundary
- That boundary exists to keep future non-DOM exploration, such as React Native, from changing the public player API too early
beta.1still ships only the browser engine