Class: CompositeMediaHandler
Defined in: packages/media-session/src/handler.ts:156
Delegating base for handler decorators — analytics, persistence, logging.
Every method forwards to inner, so a decorator overrides one method, does
its work, and calls super. Written out longhand rather than with a Proxy:
the method list is the contract, and a Proxy would silently forward methods
added to MediaHandler later without the decorator author noticing.
class LoggingHandler extends CompositeMediaHandler {
override play() { console.log('play'); return super.play() }
}
Implements
Constructors
Constructor
new CompositeMediaHandler(inner: MediaHandler): CompositeMediaHandler;
Defined in: packages/media-session/src/handler.ts:178
Parameters
| Parameter | Type |
|---|---|
inner | MediaHandler |
Returns
CompositeMediaHandler
Properties
inner
protected readonly inner: MediaHandler;
Defined in: packages/media-session/src/handler.ts:178
playFromSearch?
readonly optional playFromSearch?: (query: string, focus: SearchFocus) => void | Promise<void>;
Defined in: packages/media-session/src/handler.ts:171
Present iff the wrapped handler has it — properties rather than methods, and assigned in the constructor, for a reason that is invisible until a car is in front of you.
These two are capability declarations: MediaService.init reads the
decorated handler to decide whether the session advertises a search tab
(COMMAND_CODE_LIBRARY_SEARCH, which is the only thing that sets Android
Auto's SEARCH_SUPPORTED) and whether voice playback is answerable. A
decorator that defined them the way it defines play would advertise a
search the app underneath cannot answer, and the car would draw an empty
search tab instead of no search tab. A method declaration cannot be
conditional; a property assignment can.
Parameters
| Parameter | Type |
|---|---|
query | string |
focus | SearchFocus |
Returns
void | Promise<void>
Implementation of
search?
readonly optional search?: (query: string) => Promise<BrowseItem[]>;
Defined in: packages/media-session/src/handler.ts:176
See playFromSearch.
Parameters
| Parameter | Type |
|---|---|
query | string |
Returns
Promise<BrowseItem[]>
Implementation of
Methods
customAction()
customAction(name: string, extras?: Record<string, unknown>): void | Promise<void>;
Defined in: packages/media-session/src/handler.ts:234
One of PlaybackState.customActions was pressed.
Android only. MPRemoteCommandCenter's command set is fixed and carries
no app-defined identifier, so no iOS remote surface can invoke a custom
action and this is never called there by the session — see
MediaCustomAction. Call it from your own UI if you want one code
path on both platforms.
Parameters
| Parameter | Type | Description |
|---|---|---|
name | string | - |
extras? | Record<string, unknown> | the controller's payload, or undefined when there is none. Only Android controllers can send one. |
Returns
void | Promise<void>
Implementation of
getChildren()
getChildren(parentId: string): Promise<BrowseItem[]>;
Defined in: packages/media-session/src/handler.ts:263
The children of a browsable node — one screen of the car's browse tree.
Called with BROWSE_ROOT for the root tabs, then with whatever id
the user drilled into. The car hands ids back verbatim; nothing native
parses them.
Unlike every other method here this one is awaited: a browser expects an
answer (media3 returns a ListenableFuture, CarPlay fills a list after the
push), and it is the browser being kept waiting, not a finger on a button.
Answer fast anyway — Android Auto shows a spinner until you do.
Return [] for "this node has nothing under it" — Google's own guidance is
to prefer an empty list over an error code. Throw (or reject with) a
BrowseError for "I cannot answer this": a sign-in screen instead of
an empty one.
The root is capped at four browsable tabs on both platforms
(BROWSE_ROOT), and anything dropped is reported on the session-error
channel as browseRootRejected rather than vanishing.
Parameters
| Parameter | Type |
|---|---|
parentId | string |
Returns
Promise<BrowseItem[]>
Implementation of
getMediaItem()
getMediaItem(id: string): Promise<BrowseItem | undefined>;
Defined in: packages/media-session/src/handler.ts:266
One browse node by id — Android's onGetItem, CarPlay refreshing a row.
undefined means "no such item", which the car renders as a missing row
rather than an error. Throw a BrowseError for "it exists and you
may not have it".
Parameters
| Parameter | Type |
|---|---|
id | string |
Returns
Promise<BrowseItem | undefined>
Implementation of
onAdjustDeviceVolume()
onAdjustDeviceVolume(direction: RemoteVolumeDirection): void | Promise<void>;
Defined in: packages/media-session/src/handler.ts:223
A hardware volume key moved one notch on a backend that can only be
nudged — volumeControl: 'relative'.
Most apps never implement this. With the default 'absolute' the library
turns the notch into a level itself (one 1 / steps step from the last
published volume, quantised and clamped) and delivers
onSetDeviceVolume instead — so a Cast or UPnP backend needs one
method, not two, and the step arithmetic is written and tested once rather
than in every app.
Which of the two you get is decided by what you published, never by which
methods you defined: 'relative' → this, 'absolute' →
onSetDeviceVolume, 'fixed' → neither.
Either way, the routing is the point. With the app backgrounded or the screen locked there is no Activity to receive a key event; the platform hands the press to the media session's volume provider, which exists only because MediaServiceApi.setRemotePlayback made the session advertise remote playback.
Android only — see onSetDeviceVolume.
Parameters
| Parameter | Type |
|---|---|
direction | RemoteVolumeDirection |
Returns
void | Promise<void>
Implementation of
MediaHandler.onAdjustDeviceVolume
onPlaybackResumption()
onPlaybackResumption(): void | Promise<void>;
Defined in: packages/media-session/src/handler.ts:243
Android only: this JS runtime was started by the media service to complete a playback resumption after the process had been killed.
By the time this runs the notification is already on screen, the session
already carries the persisted track and position, and the play the user
pressed is about to be replayed on this handler. So there is nothing you
have to do here — it exists so an app can log it, fire an analytics event,
or refresh a token before the replayed play() needs one.
Optional for the same reason onSleepTimer is: an informational callback added after v1 must not break structural implementors of this interface.
Requires android.playbackResumption: true, withPersistence, and
MediaService.init at module scope — see
MediaServiceConfig.android.
Returns
void | Promise<void>
Implementation of
MediaHandler.onPlaybackResumption
onSessionError()
onSessionError(error: SessionError): void | Promise<void>;
Defined in: packages/media-session/src/handler.ts:256
Forwarded — or logged, when inner has no onSessionError.
The ?.() every other optional method uses would be a swallow here, and a
particularly good hiding place: this class defines the method, so the
service's own console floor sees a handler that implements the channel and
steps back, while the decorator quietly drops the error on the way to an
inner handler that never implemented it. Decorating a handler must not be
able to silence its errors.
Parameters
| Parameter | Type |
|---|---|
error | SessionError |
Returns
void | Promise<void>
Implementation of
onSetDeviceMuted()
onSetDeviceMuted(muted: boolean): void | Promise<void>;
Defined in: packages/media-session/src/handler.ts:228
A remote surface asked to mute or unmute the remote device. Same request/acknowledge contract as onSetDeviceVolume, and Android only for the same reason.
Parameters
| Parameter | Type |
|---|---|
muted | boolean |
Returns
void | Promise<void>
Implementation of
onSetDeviceVolume()
onSetDeviceVolume(volume: number): void | Promise<void>;
Defined in: packages/media-session/src/handler.ts:220
A remote surface asked for an absolute volume on the remote device,
0..1.
Reachable only while MediaServiceApi.setRemotePlayback has
published a device with volumeControl: 'absolute' — from the surfaces
that express a level (Android's remote volume dialog, the output switcher,
a MediaController) and from the hardware volume keys, whose notch the
library converts for you (see onAdjustDeviceVolume).
A request, not a fact — the same contract as every other handler
method. Move the backend, then republish through setRemotePlayback; that
republish is what moves the slider on every surface.
Android only. iOS gives an app no way to take over the hardware volume
buttons and MPRemoteCommandCenter has no volume command at all, so
setRemotePlayback is a documented no-op there and this — like
onAdjustDeviceVolume and onSetDeviceMuted — is never
invoked by the session on iOS. Drive a remote device's volume from your own
in-app slider there, which is what Google's own iOS cast apps do.
Optional for the reason onSleepTimer is: a method added after v1
must not break structural implementors. BaseMediaHandler supplies a
no-op.
Parameters
| Parameter | Type |
|---|---|
volume | number |
Returns
void | Promise<void>
Implementation of
MediaHandler.onSetDeviceVolume
onSetRepeatMode()
onSetRepeatMode(mode: MediaRepeatMode): void | Promise<void>;
Defined in: packages/media-session/src/handler.ts:214
A remote surface asked for a different repeat mode.
A request, not a fact. Nothing has changed until the app changes it and
broadcasts a setPlaybackState carrying the new repeatMode — the same
acknowledgement contract play/pause follow, and on Android it is
literally what completes media3's pending-operation future.
Only reachable when the app advertises the setRepeatMode capability.
Optional for the reason onSleepTimer is: this interface is the
player-agnostic contract, and a method added after v1 must not break
structural implementors. BaseMediaHandler supplies a no-op.
Parameters
| Parameter | Type |
|---|---|
mode | MediaRepeatMode |
Returns
void | Promise<void>
Implementation of
onSetShuffle()
onSetShuffle(enabled: boolean): void | Promise<void>;
Defined in: packages/media-session/src/handler.ts:217
A remote surface asked to turn shuffle on or off. See
onSetRepeatMode — same request/acknowledge contract, gated on the
setShuffle capability, optional for the same reason.
Parameters
| Parameter | Type |
|---|---|
enabled | boolean |
Returns
void | Promise<void>
Implementation of
onSleepTimer()
onSleepTimer(): void | Promise<void>;
Defined in: packages/media-session/src/handler.ts:240
The native sleep timer elapsed.
Playback is already paused when this runs. The timer fires on a
platform timer (Handler.postDelayed / DispatchQueue.main.asyncAfter),
pauses natively on the same path a notification pause takes, and only then
calls this — the ordering is deliberate and is the reason the feature is
native at all: your setTimeout would not have fired (see the README's
background-playback limits).
So this is where a timer badge is cleared, an analytics event is logged,
or stopService() is called if you would rather end background execution
than sit paused. Pausing again here is harmless but redundant.
Optional: the pause has already happened natively by the time this fires, so a handler with nothing to add can simply omit it — structural implementors of this interface (the player-agnostic contract) must not break when the library grows an informational callback.
Returns
void | Promise<void>
Implementation of
onTaskRemoved()
onTaskRemoved(): void | Promise<void>;
Defined in: packages/media-session/src/handler.ts:231
Android only: the app's task was swiped out of Recents.
The native default policy (keep playing if playing, otherwise stop the
service) has already been decided by the time this runs — overriding this
method is for side effects (persistence, analytics), not for changing that
decision. Call stopService() here to force a stop.
Returns
void | Promise<void>
Implementation of
pause()
pause(): void | Promise<void>;
Defined in: packages/media-session/src/handler.ts:193
Returns
void | Promise<void>
Implementation of
play()
play(): void | Promise<void>;
Defined in: packages/media-session/src/handler.ts:190
Returns
void | Promise<void>
Implementation of
playFromMediaId()
playFromMediaId(id: string): void | Promise<void>;
Defined in: packages/media-session/src/handler.ts:269
A car, a head unit or an assistant asked to play a browse item.
Build the queue around id, broadcast it, start playback — the same
acknowledge-by-broadcast contract as play: nothing on any surface
moves until the app's next setQueue/setPlaybackState says so.
Not optional, and deliberately: a browse tree whose leaves do nothing when
tapped is the purest silent no-op in this package. BaseMediaHandler's
default therefore reports playFromMediaIdUnhandled on the session-error
channel instead of returning quietly.
Parameters
| Parameter | Type |
|---|---|
id | string |
Returns
void | Promise<void>
Implementation of
seekTo()
seekTo(position: number): void | Promise<void>;
Defined in: packages/media-session/src/handler.ts:199
Parameters
| Parameter | Type | Description |
|---|---|---|
position | number | milliseconds |
Returns
void | Promise<void>
Implementation of
setRate()
setRate(rate: number): void | Promise<void>;
Defined in: packages/media-session/src/handler.ts:211
Parameters
| Parameter | Type |
|---|---|
rate | number |
Returns
void | Promise<void>
Implementation of
skipToNext()
skipToNext(): void | Promise<void>;
Defined in: packages/media-session/src/handler.ts:202
Returns
void | Promise<void>
Implementation of
skipToPrevious()
skipToPrevious(): void | Promise<void>;
Defined in: packages/media-session/src/handler.ts:205
Returns
void | Promise<void>
Implementation of
skipToQueueItem()
skipToQueueItem(index: number): void | Promise<void>;
Defined in: packages/media-session/src/handler.ts:208
Play an arbitrary entry of the broadcast queue.
Reached from a remote surface on Android only — Android Auto, Wear, a
car head unit, any controller that renders the queue
(Player.COMMAND_SEEK_TO_MEDIA_ITEM). MPRemoteCommandCenter has no
queue-jump command, so no iOS remote surface can invoke this; it is still
part of the interface because your own UI calls it on both platforms.
Parameters
| Parameter | Type | Description |
|---|---|---|
index | number | index into the last broadcast queue |
Returns
void | Promise<void>
Implementation of
stop()
stop(): void | Promise<void>;
Defined in: packages/media-session/src/handler.ts:196
Release resources. Does NOT end background execution — call stopService() for that.
Returns
void | Promise<void>