Skip to main content

Interface: NativeMediaItem

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

Metadata for the item currently playing.

Properties

album?

optional album?: string;

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


albumArtist?

optional albumArtist?: string;

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

Album artist — the compilation/various-artists discriminator, and a different field from artist in every tag format there is.

Android: MediaMetadata.Builder.setAlbumArtist(CharSequence).

iOS: published as MPMediaItemPropertyAlbumArtist, with a caveat worth stating rather than hiding. The key is real (it is one of MPMediaItem's "Filterable property keys", developer.apple.com/documentation/mediaplayer/mpmediaitem, read 2026-08-16), but Apple does not document which keys nowPlayingInfo actually renders — the property's own Discussion says only "To clear the now playing info center dictionary, set it to nil". Unknown keys are ignored, so sending it is free; it goes out for the surfaces that do read it, and no promise is made that the lock screen shows it.


artist?

optional artist?: string;

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


artworkUri?

optional artworkUri?: string;

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

http(s)://, file:// or content://. Loaded async, never on the caller.


discNumber?

optional discNumber?: number;

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

1-based disc number for multi-disc releases.

Android: MediaMetadata.Builder.setDiscNumber(Integer). iOS: MPMediaItemPropertyDiscNumber.


duration?

optional duration?: number;

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

Duration in ms. Omit for live/unknown.

Broadcasting it through setMediaItem is enough even when the track also sits in a queue: for the current entry the two channels are merged (see MediaServiceApi.setMediaItem). Without a duration Android greys out the scrubber and iOS marks the track as a live stream.


extras?

optional extras?: Record<string, string>;

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

Opaque, app-owned key/value payload, round-tripped through the session and through persistence untouched.

Every competitor has this and the absence forces an app to keep a side table keyed by id — which then has to be rebuilt after process death, exactly when the app has least to work with.

String values only, deliberately. Nitro would happily carry Record<string, AnyValue>, but the two destinations cannot: Android puts these in a MediaMetadata Bundle that crosses a binder to third-party controllers, and the record is serialized to JSON by withPersistence. A string map survives both without a type-erasure story; anything richer would need one and would still arrive back as unknown. Stringify at the edge and the round trip is total.

On iOS this is carried and persisted but not published: MediaPlayer has no arbitrary-payload key (the two opaque string keys it does have — MPNowPlayingInfoPropertyExternalContentIdentifier and MPNowPlayingInfoCollectionIdentifier — both have defined system meanings and are not extras). That is the point of the field either way: it exists so the app gets its own data back, not so the OS renders it.


genre?

optional genre?: string;

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


id

id: string;

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

Stable id. Doubles as the media3 MediaItem.mediaId.

Remarks

Use a stable id per source, and let duplicates be duplicates.

The same id may appear more than once in a queue, and it routinely does: "play next" on a track that is already queued produces exactly that, and so does a repeat-one-song-twice playlist. Nothing here rejects it and nothing misbehaves because of it — position is carried by queueIndex, and media3's timeline uids are built as "$index:${item.id}" (BroadcastPlayer.kt), which stays unique even when the ids do not.

Where the id is load-bearing

Two places, both of which want the id to mean "this source", not "this row":

  1. The channel-2 merge. setMediaItem enriches the current queue entry field-by-field, and only when item.id equals the id of the entry at the broadcast queueIndex (Snapshot.kt's timeline). Matching on the id at a known index is what makes the merge well-defined in the presence of duplicates — it never has to ask "which of the two copies did you mean". When the ids disagree the queue entry wins unchanged and Android logs the mismatch; that combination means the two broadcasts got out of step, and it is usually visible as a missing scrubber (the duration is the field that normally arrives only via setMediaItem).
  2. Restoring a persisted session. A restored record is matched back to the app's catalogue by id. An id that was minted per insertion rather than per source — track-7#2, ${id}-${Date.now()}, an array index — does not exist any more by the time the app cold-starts, so the match fails and the session comes back blank.

The rule

Derive the id from the thing being played (its catalogue id, or its URI) — never from its position in the queue, and never uniquified with a counter or a timestamp to "avoid" duplicates. Suffixing to make ids unique breaks resumption and buys nothing, because nothing here needed them unique.


isLive?

optional isLive?: boolean;

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

true for a live stream (radio, a live event) whose position is not a place in a finite thing.

Until now the absence of a duration was this package's only live/unknown discriminator, which conflated two different facts: "this is live" and "I do not know the duration yet". This makes the first one sayable. When it is true the surfaces stop offering a scrubber even if a duration is also present — Android sets MediaItemData.isDynamic and drops seekability, iOS sets MPNowPlayingInfoPropertyIsLiveStream.

Omitted is not false: omitted keeps the old rule (live iff there is no duration), so nothing that worked before changes.


subtitle?

optional subtitle?: string;

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

A secondary line — podcast episode subtitle, audiobook chapter name, radio show name.

Android: MediaMetadata.Builder.setSubtitle(CharSequence), which media3's own notification reads through getNotificationContentText. iOS has no third line. The lock screen draws title / artist / album and nothing else, and the complete MPMediaItemProperty* / MPNowPlayingInfoProperty* key set was read for a fourth (developer.apple.com/documentation/mediaplayer/mpmediaitem and .../mpnowplayinginfocenter, read 2026-08-16): the only free-text keys left are MPMediaItemPropertyComments, MPMediaItemPropertyLyrics and MPMediaItemPropertyPodcastTitle — all with defined, different meanings — and MPNowPlayingInfoPropertyServiceIdentifier, documented as an opaque provider-coordination id that is never displayed. The ecosystem's usual workaround is to fold the subtitle into artist or album; doing that here would corrupt two fields the app also sets, so this is carried through the session and through persistence and not published (same as year).


title

title: string;

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


trackNumber?

optional trackNumber?: number;

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

1-based track number within the album.

Android: MediaMetadata.Builder.setTrackNumber(Integer). iOS: MPMediaItemPropertyAlbumTrackNumber.


year?

optional year?: number;

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

Release year, e.g. 1997.

Android maps to MediaMetadata.Builder.setReleaseYear(Integer) rather than setRecordingYear: media3 carries both, tag formats mostly carry one, and "the year on the cover" is the release year. setRecordingYear is left unset instead of being filled with the same number, because inventing a recording date we were never told is exactly the kind of quiet lie this package refuses elsewhere.

iOS has no year key at all, checked rather than assumed: MediaPlayer's complete key space was enumerated (MPMediaItem's "General media item property keys" and "Filterable property keys", plus MPNowPlayingInfoCenter's "Accessing Now Playing metadata properties"; developer.apple.com/documentation/mediaplayer/mpmediaitem and .../mpnowplayinginfocenter, read 2026-08-16) and there is no MPMediaItemPropertyYear. The one date-shaped key, MPMediaItemPropertyReleaseDate, is an NSDate — and a bare year is not a date. So on iOS this field is carried through the session and through persistence and is simply not published. A synthesised NSDate of "1 January <year>" would be a fabricated precision, which is worse than the gap.