Skip to main content

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

ParameterType
innerMediaHandler

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

ParameterType
querystring
focusSearchFocus

Returns

void | Promise<void>

Implementation of

MediaHandler.playFromSearch


readonly optional search?: (query: string) => Promise<BrowseItem[]>;

Defined in: packages/media-session/src/handler.ts:176

See playFromSearch.

Parameters

ParameterType
querystring

Returns

Promise<BrowseItem[]>

Implementation of

MediaHandler.search

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

ParameterTypeDescription
namestring-
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

MediaHandler.customAction


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

ParameterType
parentIdstring

Returns

Promise<BrowseItem[]>

Implementation of

MediaHandler.getChildren


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

ParameterType
idstring

Returns

Promise<BrowseItem | undefined>

Implementation of

MediaHandler.getMediaItem


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

ParameterType
directionRemoteVolumeDirection

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

ParameterType
errorSessionError

Returns

void | Promise<void>

Implementation of

MediaHandler.onSessionError


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

ParameterType
mutedboolean

Returns

void | Promise<void>

Implementation of

MediaHandler.onSetDeviceMuted


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

ParameterType
volumenumber

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

ParameterType
modeMediaRepeatMode

Returns

void | Promise<void>

Implementation of

MediaHandler.onSetRepeatMode


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

ParameterType
enabledboolean

Returns

void | Promise<void>

Implementation of

MediaHandler.onSetShuffle


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

MediaHandler.onSleepTimer


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

MediaHandler.onTaskRemoved


pause()

pause(): void | Promise<void>;

Defined in: packages/media-session/src/handler.ts:193

Returns

void | Promise<void>

Implementation of

MediaHandler.pause


play()

play(): void | Promise<void>;

Defined in: packages/media-session/src/handler.ts:190

Returns

void | Promise<void>

Implementation of

MediaHandler.play


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

ParameterType
idstring

Returns

void | Promise<void>

Implementation of

MediaHandler.playFromMediaId


seekTo()

seekTo(position: number): void | Promise<void>;

Defined in: packages/media-session/src/handler.ts:199

Parameters

ParameterTypeDescription
positionnumbermilliseconds

Returns

void | Promise<void>

Implementation of

MediaHandler.seekTo


setRate()

setRate(rate: number): void | Promise<void>;

Defined in: packages/media-session/src/handler.ts:211

Parameters

ParameterType
ratenumber

Returns

void | Promise<void>

Implementation of

MediaHandler.setRate


skipToNext()

skipToNext(): void | Promise<void>;

Defined in: packages/media-session/src/handler.ts:202

Returns

void | Promise<void>

Implementation of

MediaHandler.skipToNext


skipToPrevious()

skipToPrevious(): void | Promise<void>;

Defined in: packages/media-session/src/handler.ts:205

Returns

void | Promise<void>

Implementation of

MediaHandler.skipToPrevious


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

ParameterTypeDescription
indexnumberindex into the last broadcast queue

Returns

void | Promise<void>

Implementation of

MediaHandler.skipToQueueItem


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>

Implementation of

MediaHandler.stop