Skip to main content

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 setPlaybackState returns;
  • 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 setItem completion 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​

ParameterType
serviceMediaServiceApi
storageMediaSessionStorage
optionsPersistenceOptions

Returns​

PersistedMediaService