Skip to main content

Interface: RnMediaMediaSession

Defined in: packages/media-session/src/specs/media-session.nitro.ts:1453

The process-wide media session.

A singleton by nature, not by convenience: MediaSessionService and MPNowPlayingInfoCenter are both process-singular (CLAUDE.md principle 5's documented exception).

Named initialize rather than init because init is a Swift keyword and nitrogen emits the method name verbatim into the generated Swift protocol.

Extends

  • HybridObject<{ android: "kotlin"; ios: "swift"; }>

Properties

name

readonly name: string;

Defined in: node_modules/react-native-nitro-modules/lib/typescript/HybridObject.d.ts:52

The HybridObject's name.

Inherited from

HybridObject.name

Methods

cancelSleepTimer()

cancelSleepTimer(): void;

Defined in: packages/media-session/src/specs/media-session.nitro.ts:1635

Disarm the sleep timer. A no-op when none is armed.

Returns

void


dispose()

dispose(): void;

Defined in: node_modules/react-native-nitro-modules/lib/typescript/HybridObject.d.ts:94

Disposes any resources this HybridObject might hold natively, and releases this HybridObject's NativeState.

After calling (), this object can no longer be used.

Eagerly disposing a HybridObject could be beneficial for a queue-/handler-architecture where a bunch of Hybrid Objects are allocated, and later deallocated once a callback (e.g. a render function) completes.

Returns

void

Note

It is NOT required to call () manually, as the JavaScript Garbage Collector automatically disposes and releases any resources when needed. It is purely optional to eagerly-, and manually-, call () here - use with caution!

Inherited from

HybridObject.dispose

equals()

equals(other: HybridObject<{
android: "kotlin";
ios: "swift";
}>): boolean;

Defined in: node_modules/react-native-nitro-modules/lib/typescript/HybridObject.d.ts:79

Returns whether this HybridObject is the same object as other.

While two HybridObjects might not be equal when compared with ==, they might still hold the same underlying HybridObject, in which case equals(other) will return true.

Parameters

ParameterType
otherHybridObject<{ android: "kotlin"; ios: "swift"; }>

Returns

boolean

Example

const hybridA = SomeModule.getExistingHybridInstance()
const hybridB = SomeModule.getExistingHybridInstance()
console.log(hybridA.equals(hybridB)) // true

Inherited from

HybridObject.equals

getCarConnection()

getCarConnection(): string;

Defined in: packages/media-session/src/specs/media-session.nitro.ts:1698

Is a car connected right now — 'none' | 'androidAuto' | 'automotiveOs' | 'carPlay'.

Synchronous and cheap (a field read). Android derives it from the set of connected controllers media3 classifies as the Auto companion (com.google.android.projection.gearhead) or Automotive OS (com.android.car.media, com.android.car.carlauncher); iOS from whether a CPTemplateApplicationScene is connected. The reactive twin is the useCarConnection() hook, fed by MediaSessionHandlers.onCarConnectionChanged.

Returns

string


getSleepTimer()

getSleepTimer(): NativeSleepTimerState | undefined;

Defined in: packages/media-session/src/specs/media-session.nitro.ts:1657

The armed timer's mode and — when knowable — its remaining seconds, or undefined when none is armed.

The structured form of getSleepTimerRemaining, which cannot describe a trackEnd timer whose deadline is not yet computable. Same clock, same synchronous cheapness.

Returns

NativeSleepTimerState | undefined


getSleepTimerRemaining()

getSleepTimerRemaining(): number | undefined;

Defined in: packages/media-session/src/specs/media-session.nitro.ts:1647

Seconds until the armed timer fires, or undefined when none is armed.

Read from the same clock the timer was scheduled againstSystemClock.uptimeMillis() on Android (what Handler.postDelayed uses) and DispatchTime.now() on iOS — so it can never disagree with when the pause will actually happen, and a wall-clock change cannot move it. Synchronous and cheap: it is meant to be polled by a UI that is on screen, which is the one place a JS timer does work.

Returns

number | undefined


initialize()

initialize(config: MediaSessionConfig, handlers: MediaSessionHandlers): Promise<void>;

Defined in: packages/media-session/src/specs/media-session.nitro.ts:1468

Install the handlers and platform configuration.

Android: creates the notification channel and the MediaLibrarySession (bound to a facade SimpleBasePlayer). It does not start the foreground service — that happens on the first play while the app is startable, per the Android 12+ background-FGS-start restriction. iOS: installs MPRemoteCommandCenter targets.

Rejects when called twice without an intervening stopService.

Parameters

ParameterType
configMediaSessionConfig
handlersMediaSessionHandlers

Returns

Promise<void>


invalidateBrowse()

invalidateBrowse(parentId?: string): void;

Defined in: packages/media-session/src/specs/media-session.nitro.ts:1684

The children of parentId changed — a download finished, a library synced, the user signed in.

Evicts the native browse cache entry and tells every connected browser: Android calls MediaLibrarySession.notifyChildrenChanged, iOS re-fetches and re-sections any visible template showing that parent. Omit parentId to mean "everything", which evicts the whole cache and notifies for every parent that was in it plus the root.

Parameters

ParameterType
parentId?string

Returns

void


setBrowseCapabilities()

setBrowseCapabilities(caps: NativeBrowseCapabilities): void;

Defined in: packages/media-session/src/specs/media-session.nitro.ts:1672

Declare which browse capabilities the app's handler actually implements.

Called by the TS layer at initialize and whenever the handler is replaced. Android turns search: false into the removal of COMMAND_CODE_LIBRARY_SEARCH from the session commands granted to browsers, which is the only thing that makes media3's legacy stub advertise android.media.browse.SEARCH_SUPPORTED = false — Android Auto hides its search tab from that key alone. iOS has no search surface, so this is recorded and otherwise unused there.

Parameters

ParameterType
capsNativeBrowseCapabilities

Returns

void


setMediaItem()

setMediaItem(item?: NativeMediaItem): void;

Defined in: packages/media-session/src/specs/media-session.nitro.ts:1477

Broadcast channel 2 of 3. Pass nothing to clear the metadata.

Parameters

ParameterType
item?NativeMediaItem

Returns

void


setPlaybackState()

setPlaybackState(state: NativePlaybackState): void;

Defined in: packages/media-session/src/specs/media-session.nitro.ts:1474

Broadcast channel 1 of 3. See NativePlaybackState.

Parameters

ParameterType
stateNativePlaybackState

Returns

void


setQueue()

setQueue(items: NativeMediaItem[]): void;

Defined in: packages/media-session/src/specs/media-session.nitro.ts:1480

Broadcast channel 3 of 3. Pass [] to clear the queue.

Parameters

ParameterType
itemsNativeMediaItem[]

Returns

void


setRemotePlayback()

setRemotePlayback(remote?: NativeRemotePlayback): void;

Defined in: packages/media-session/src/specs/media-session.nitro.ts:1554

Declare that playback is currently coming out of another device, and carry that device's volume — or pass nothing to say it is local again.

Not a fourth broadcast channel: it says nothing about what is playing. It is a routing statement, in the same family as setResumptionSnapshot — the session needs it, no surface renders the app's playback state from it, and it is sticky (a setPlaybackState does not clear it) because "the audio is on the speaker" is a mode, not a per-broadcast fact.

Android: this is what makes hardware volume keys reach the speaker

The facade Player starts reporting DeviceInfo.PLAYBACK_TYPE_REMOTE with the published range, and advertises COMMAND_GET_DEVICE_VOLUME plus the set/adjust commands implied by NativeRemotePlayback.volumeControl. media3's MediaSessionLegacyStub reacts to the DeviceInfo change by calling MediaSessionCompat.setPlaybackToRemote(volumeProvider) on the platform session — and the platform's own contract for that call is the whole feature:

Configure this session to use remote volume handling. This must be called to receive volume button events, otherwise the system will adjust the appropriate stream volume for this session. — android.media.session.MediaSession.setPlaybackToRemote

Because the routing lives on the session, it works with the app backgrounded and the screen locked, which is exactly where an Activity's dispatchKeyEvent cannot help. Presses arrive at MediaSessionHandlers.increaseDeviceVolume / MediaSessionHandlers.decreaseDeviceVolume.

Passing nothing restores local handling: media3 sees the DeviceInfo go back to PLAYBACK_TYPE_LOCAL and calls setPlaybackToLocal, after which the keys move the phone's music stream again. There is no state left behind — the transition is driven by the same value both ways.

iOS: honestly, nothing

A no-op, and not for lack of trying: iOS gives an app no way to take over the hardware volume buttons. MPVolumeView renders the system volume slider, AVAudioSession.outputVolume is read-only, and Google's own GCKUICastContainerViewController.physicalVolumeButtonsWillControlDeviceVolume is documented as having no effect since iOS 15. The buttons therefore keep controlling the phone. Calling this on iOS is harmless and free — the same app code runs on both platforms — it simply changes nothing there.

Parameters

ParameterType
remote?NativeRemotePlayback

Returns

void


setResumptionSnapshot()

setResumptionSnapshot(snapshot?: string): void;

Defined in: packages/media-session/src/specs/media-session.nitro.ts:1506

Mirror the persisted session into native-owned storage, for Android playback resumption.

Not a fourth broadcast channel and not an app-facing call: withPersistence invokes this with the very same JSON record it hands the app's storage engine, so the two copies cannot drift. Pass undefined to forget it.

Why a native mirror exists at all

The whole point of playback resumption is that it works with no JavaScript in the process. The service is created by the OS, has ~5 s to call startForeground, and must already know what track to show. Reading the app's storage engine is not an option: that engine is JavaScript, and JavaScript is precisely what is missing. So the record is also written to SharedPreferences owned by this package — survives process death, read back synchronously on the service's main thread, no bridge involved (ARCHITECTURE §9, native-first).

The JS-side storage stays the app-facing source of truth; this is a cache that only the resumption path reads. Ignored on iOS, which has no service to resurrect.

Parameters

ParameterTypeDescription
snapshot?stringthe serialized PersistedSession record, or undefined.

Returns

void


setSleepTimer()

setSleepTimer(seconds: number): void;

Defined in: packages/media-session/src/specs/media-session.nitro.ts:1601

Pause playback in seconds. Replaces any timer already armed.

Why this is native and not setTimeout

A JS sleep timer is broken by construction on Android: RN's JavaTimerManager gates timers on the Activity lifecycle plus headless tasks, so with no Activity they simply stop firing — and Samsung freezes them even with one (RN #56324). A sleep timer is the one feature whose entire job happens after the user has put the phone down, i.e. exactly when JS timers do not run. So it lives on a platform timer: a main-looper Handler.postDelayed on Android, a DispatchQueue.main.asyncAfter work item on iOS. Neither is tied to an Activity, and neither is a JS timer.

What happens when it fires

  1. Playback is paused natively, on the identical path a notification pause takes — the facade Player is paused, so the session, the notification and the lock screen all go to paused immediately, and the app's pause handler is invoked to actually stop the audio.
  2. MediaSessionHandlers.onSleepTimer is invoked fire-and-forget.

The timer is armed even with no Activity alive; it does not survive process death (nothing does — see the README's background-playback limits) and it is cancelled by stopService and by a dev reload.

iOS note, stated honestly: iOS suspends a backgrounded process shortly after audio stops, and a suspended process runs no timers. That is not a problem for this feature — while audio is playing the process is not suspended, so a timer armed during playback fires. What cannot be relied on is a timer armed (or still pending) while nothing is playing.

Parameters

ParameterTypeDescription
secondsnumberstrictly positive; the TS layer rejects 0, negatives and non-finite values before they reach here.

Returns

void


setSleepTimerToTrackEnd()

setSleepTimerToTrackEnd(): void;

Defined in: packages/media-session/src/specs/media-session.nitro.ts:1632

Pause when the current item finishes. Replaces any timer already armed.

The mode most sleep-timer users actually want, and the one a JS timer cannot express even in the foreground: "30 minutes" cuts a track in half, "end of this track" does not.

How a package with no playback engine knows when a track ends

From the three broadcast channels, which is all it ever knows — and that is enough, because they already carry the two numbers involved: the position anchor ({value, at, rate}) and the current item's duration. The deadline is (duration - projectedPosition) / rate, computed natively and re-armed on every broadcast, so a seek, a pause, a rate change or a duration that arrives late all move it. No polling, no timer on the JS side, nothing streamed across the bridge — the existing discontinuity-only contract is exactly the update rate this needs.

Two cases it handles without a duration at all:

  • The current item changes (the track ended and the app advanced, or the user skipped) — the timer fires immediately. That is the honest reading of "stop after this one", and it is what makes the feature work on a live stream or before a duration is known.
  • The app never broadcasts a duration — armed with no deadline, waiting for the item change above. getSleepTimer reports trackEnd with no remainingSeconds rather than pretending.

When it fires the behaviour is identical to setSleepTimer: playback is paused natively first, then MediaSessionHandlers.onSleepTimer.

Returns

void


stopService()

stopService(): Promise<void>;

Defined in: packages/media-session/src/specs/media-session.nitro.ts:1564

End background execution.

Android: releases the session and stops the foreground service. This is the ONLY thing that does — pause() never does (PLAN §5.4). iOS: clears the now-playing info and disables every remote command; the process then lives or dies on whether audio is still playing.

Returns

Promise<void>


toString()

toString(): string;

Defined in: node_modules/react-native-nitro-modules/lib/typescript/HybridObject.d.ts:64

Returns a string representation of the given HybridObject.

Unless overridden by the HybridObject, this will return the name of the object.

Returns

string

Example

const hybridA = SomeModule.getExistingHybridInstance()
console.log(hybridA.toString()) // [HybridObject HybridA]

Inherited from

HybridObject.toString