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.
Duplicates are legal
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":
- The channel-2 merge.
setMediaItemenriches the current queue entry field-by-field, and only whenitem.idequals the id of the entry at the broadcastqueueIndex(Snapshot.kt'stimeline). 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 viasetMediaItem). - 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.