Skip to main content

Android Auto

The library registers the Media3 browse + playback service in its manifest, so Android Auto support is wired automatically. Your job is to supply the browse tree and resolve play requests.

Android Auto here is phone-projected Auto. Native Android Automotive OS (AAOS) is not supported.

1. Initialize on cold start​

Android Auto can launch your app's JavaScript without any UI, so initialize the player and register your handlers from a bootstrap module imported before your app entry — see Setup & manual configuration.

2. Provide a browse tree​

There are two complementary ways to feed the browser:

  • setBrowseSnapshot(snapshot) — push a full, pre-computed tree up front (fast, no per-node round trips).
  • onBrowseRequest(parentId) — resolve a node lazily on drilldown.
import { getTrackPlayer, registerPlaybackService } from 'react-native-queue-player';

// Push a top-level snapshot once you have data.
getTrackPlayer().setBrowseSnapshot({
rootId: 'root',
sections: [
{
id: 'playlists',
title: 'Playlists',
items: [
{ id: 'pl:chill', title: 'Chill', playable: false, hasChildren: true },
{ id: 'pl:focus', title: 'Focus', playable: false, hasChildren: true },
],
},
],
});

Register handlers for lazy browse, search, and playback resolution:

registerPlaybackService(() => ({
// Drill into a node (e.g. open a playlist).
onBrowseRequest: async (parentId) => {
const tracks = await fetchPlaylist(parentId);
return tracks.map((t) => ({
id: t.id, title: t.title, subtitle: t.artist, artworkUrl: t.artworkUrl, playable: true,
}));
},

// Live search box in the car UI.
onSearchRequest: async (query) => {
const results = await search(query);
return results.map((t) => ({ id: t.id, title: t.title, playable: true }));
},

// Resolve a browse item the user tapped into actual tracks to play.
onPlayFromIdRequest: async (mediaId) => {
const tracks = await resolveToTracks(mediaId);
return tracks; // TrackItem[]
},

// Optional connection lifecycle.
onCarConnect: () => {},
onCarDisconnect: () => {},
}));

When a request resolves to TrackItem[], the library sets the queue and starts playback for you.

Connection status​

onCarConnect and onCarDisconnect are one-shot events — they fire when the first automotive controller connects and the last one drops. On a cold start the connect can fire before your bootstrap has registered the handler, so don't rely on the event alone.

isCarConnected() is the pull-based counterpart: it returns whether a controller is connected right now, so you can check at any time and gate car-only work — like pushing a fresh browse snapshot — on the current connection.

import { getTrackPlayer } from 'react-native-queue-player';

const player = getTrackPlayer();
if (player.isCarConnected()) {
player.setBrowseSnapshot(await buildSnapshot());
}

It holds the same state the events transition between (the headless Android Auto / Wear / Assistant controllers here; the CarPlay scene on iOS) and is process-scoped — a fresh app process starts disconnected until a controller connects within it.

Service rebirth​

onServiceReady(reason) fires when the platform media service is freshly bound to JS: reason is 'first-bind' on the initial bind of a JS process and 'service-reborn' whenever a different service instance is bound afterwards — after Android killed the service (memory reclaim, doze, the idle-bucket reaper), or after your own configure() / destroy() re-bind. A reborn service starts with an empty browse tree — the snapshot you pushed is gone, and a controller binding into the new instance sees an empty library until you push it again.

Current limitation: handlers registered with registerPlaybackService are held by the service instance, not by the player, so a reborn instance has none of them — 'service-reborn' is not delivered to a handler registered before the rebirth, and neither are onBrowseRequest or onCarConnect. A service killed by the OS therefore comes back with an empty library and no signal, and a 'service-reborn' handler cannot repair it. Register again from your app's own lifecycle (an AppState change, a screen mount) and push the snapshot there if you need to survive it. The fix is tracked as #244.

Android-only. iOS has no separate service lifecycle — the app dies whole under memory pressure — so the handler is accepted for API parity and never invoked there; onCarConnect is the iOS-side attach signal.

Types​

type BrowseSnapshot = { rootId: string; sections: BrowseSection[] };
type BrowseSection = { id: string; title: string; icon?: SectionIcon; artworkUrl?: string; items: BrowseItem[] };
type BrowseItem = { id: string; title: string; subtitle?: string; artworkUrl?: string; playable: boolean; hasChildren?: boolean };

SectionIcon is an exported enum of standard section glyphs (Home, Library, Playlists, Albums, Artists, Songs, RecentlyPlayed, …).

Runtime permission​

Request POST_NOTIFICATIONS at runtime (Android 13+) so the media notification appears.

See also: CarPlay (the same handler model on iOS) and Voice (Google Assistant).