Function: withPersistence()
function withPersistence(
service: MediaServiceApi,
storage: MediaSessionStorage,
options?: PersistenceOptions
): PersistedMediaService;
Defined in: packages/media-session/src/persistence.ts:467
Tee the three broadcast channels into storage, so a session survives
process death.
A decorator over MediaServiceApi rather than a MediaHandler
decorator: the handler is the fan-in side (commands coming back), and what
needs saving is the fan-out state. Wrapping the service means an app writes
one line at init and every existing broadcast call site persists itself.
const service = withPersistence(await MediaService.init(...), AsyncStorage)
service.setQueue(items) // saved
service.setPlaybackState(state) // saved
Writes happen on discontinuities, plus one checkpoint per 30 s of playback
The three broadcast setters are the primary trigger, and those are already discontinuity-only by the package's central design rule (ARCHITECTURE §7) — nothing here polls a player, and no state is ever read on a tick.
On top of that, PersistenceOptions.autosave (on by default) re-arms a
single JS timer while the last broadcast said playing, and each tick
re-projects the anchor the app already gave us and writes it. That exists
because the discontinuity rule, applied alone, means a track played straight
through writes nothing — the position on disk stays at the last
play/seek/track change for the length of the track. The cost is one storage
setItem per interval and no bridge traffic at all: a tick deliberately does
not refresh the native resumption mirror, because that call crosses into
native and a per-tick native call is precisely what this package forbids.
Broadcasts and PersistedMediaService.save refresh both copies.
Write scheduling
The latest snapshot always wins and writes never overlap:
- a synchronous storage engine is written through synchronously, inside
the broadcast call, so the record is durable before
setPlaybackStatereturns; - an asynchronous one gets at most one write in flight; snapshots
produced while it is pending collapse into a single follow-up write. Three
channel broadcasts in one tick therefore cost one round trip, not three,
and an out-of-order
setItemcompletion can never resurrect stale state.
Two copies, one string (Android playback resumption)
Every record is also handed to the native side through
MediaServiceApi.setResumptionSnapshot, which keeps it in this package's own
SharedPreferences. That copy exists for exactly one caller: the Android
media service when the OS creates it into a process with no JavaScript in
it, which is the whole premise of playback resumption — it has ~5 s to call
startForeground and must already know what to show. The app's storage
engine stays the source of truth; the mirror is a cache, written from the
same serialized string so the two cannot drift. Inert unless
android.playbackResumption is on, and absent on iOS.
What is not persisted
Handlers (they are code), and the stopService lifecycle. stopService()
deliberately leaves the record intact: "stop" ends background execution,
not the user's place in the album. Call clearPersisted for that.
Parameters
| Parameter | Type |
|---|---|
service | MediaServiceApi |
storage | MediaSessionStorage |
options | PersistenceOptions |