Android Auto & CarPlay

RNTP V5 includes full Android Auto and CarPlay support. Users can browse and play audio from their car dashboard without touching their phone.

Setup

1. Declare the media browser service

In your AndroidManifest.xml, add the androidx.media3.session.MediaSessionService declaration:

<service
  android:name="com.doublesymmetry.trackplayer.service.HeadlessJsMediaService"
  android:exported="true"
  android:foregroundServiceType="mediaPlayback">
  <intent-filter>
    <action android:name="androidx.media3.session.MediaSessionService" />
    <action android:name="android.media.browse.MediaBrowserService" />
  </intent-filter>
</service>

2. Add the automotive app descriptor

Create res/xml/automotive_app_desc.xml:

<?xml version="1.0" encoding="utf-8"?>
<automotiveApp>
  <uses name="media" />
</automotiveApp>

Reference it in AndroidManifest.xml:

<meta-data
  android:name="com.google.android.gms.car.application"
  android:resource="@xml/automotive_app_desc" />

Setting the browse tree

Android Auto displays a browse tree when the user opens your app on the dashboard. Configure it with setBrowseTree:

import TrackPlayer, { BrowseCategory } from '@rntp/player';

await TrackPlayer.setBrowseTree([
  {
    mediaId: 'recent',
    title: 'Recently Played',
    items: [
      {
        mediaId: 'track-1',
        url: 'https://example.com/track1.mp3',
        title: 'Episode 42',
        artist: 'My Podcast',
        artworkUrl: 'https://example.com/artwork.jpg',
      },
    ],
  },
  {
    mediaId: 'favorites',
    title: 'Favorites',
    items: [...],
  },
]);

BrowseCategory

FieldTypeDescription
mediaIdstringUnique category identifier
titlestringCategory display name
itemsBrowseItem[]Items in this category — tracks or browsable containers

BrowseItem

Each item is either playable (has url) or browsable (has children):

FieldTypeDescription
mediaIdstringUnique identifier
titlestringDisplay title
artiststring?Artist or subtitle
artworkUrlstring?Artwork image URL
urlstring?Audio URL — makes this item playable
durationnumber?Duration hint in seconds
isLiveboolean?Whether this is a live stream
childrenBrowseItem[]?Child items — makes this item browsable

Nested browsing

Items can contain children to create drill-down navigation (e.g. albums containing tracks):

TrackPlayer.setBrowseTree([
  {
    mediaId: 'music',
    title: 'Music',
    items: [
      {
        mediaId: 'album-1',
        title: 'Chill Vibes',
        artist: 'Various Artists',
        artworkUrl: 'https://example.com/album-art.jpg',
        children: [
          {
            mediaId: 'track-1',
            url: 'https://example.com/track1.mp3',
            title: 'Sunset',
            artist: 'Artist A',
          },
          {
            mediaId: 'track-2',
            url: 'https://example.com/track2.mp3',
            title: 'Moonrise',
            artist: 'Artist B',
          },
        ],
      },
    ],
  },
]);

When a user selects a playable item, its siblings become the queue. Maximum nesting depth is 4 levels.

iOS — CarPlay

CarPlay lets users browse and play audio from your app on the car’s built-in display. CarPlay requires adopting the iOS scene lifecycle — your app needs a PhoneSceneDelegate for the phone UI and RNTP provides RNTPCarPlaySceneDelegate for the car display.

1. Add the CarPlay entitlement

In Xcode, open your target’s Signing & Capabilities tab and add the CarPlay Audio entitlement. This inserts the following into your .entitlements file:

<key>com.apple.developer.carplay-audio</key>
<true/>

2. Adopt the scene lifecycle

CarPlay uses iOS scenes. This means your app’s window creation must move from AppDelegate to a PhoneSceneDelegate. This is a one-time migration.

Update AppDelegate.swift — remove the window creation, keep only the factory setup:

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
  var reactNativeDelegate: ReactNativeDelegate?
  var reactNativeFactory: RCTReactNativeFactory?

  func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
  ) -> Bool {
    let delegate = ReactNativeDelegate()
    let factory = RCTReactNativeFactory(delegate: delegate)
    delegate.dependencyProvider = RCTAppDependencyProvider()

    reactNativeDelegate = delegate
    reactNativeFactory = factory

    return true
  }
}

Create PhoneSceneDelegate.swift in your app target — this takes over window creation:

import UIKit

class PhoneSceneDelegate: UIResponder, UIWindowSceneDelegate {
  var window: UIWindow?

  func scene(
    _ scene: UIScene,
    willConnectTo session: UISceneSession,
    options connectionOptions: UIScene.ConnectionOptions
  ) {
    guard let windowScene = scene as? UIWindowScene else { return }
    guard let appDelegate = UIApplication.shared.delegate as? AppDelegate,
          let factory = appDelegate.reactNativeFactory else { return }

    let window = UIWindow(windowScene: windowScene)
    factory.startReactNative(
      withModuleName: "YourAppName",
      in: window,
      launchOptions: nil
    )
    self.window = window
    window.makeKeyAndVisible()
  }
}

3. Add the scene manifest to Info.plist

Register both the phone and CarPlay scenes:

<key>UIApplicationSceneManifest</key>
<dict>
  <key>UIApplicationSupportsMultipleScenes</key>
  <false/>
  <key>UISceneConfigurations</key>
  <dict>
    <key>UIWindowSceneSessionRoleApplication</key>
    <array>
      <dict>
        <key>UISceneConfigurationName</key>
        <string>Phone</string>
        <key>UISceneClassName</key>
        <string>UIWindowScene</string>
        <key>UISceneDelegateClassName</key>
        <string>$(PRODUCT_MODULE_NAME).PhoneSceneDelegate</string>
      </dict>
    </array>
    <key>CPTemplateApplicationSceneSessionRoleApplication</key>
    <array>
      <dict>
        <key>UISceneConfigurationName</key>
        <string>CarPlay</string>
        <key>UISceneClassName</key>
        <string>CPTemplateApplicationScene</string>
        <key>UISceneDelegateClassName</key>
        <string>RNTPCarPlaySceneDelegate</string>
      </dict>
    </array>
  </dict>
</dict>

4. Set the browse tree

CarPlay uses the same setBrowseTree() call as Android Auto — no extra code required:

TrackPlayer.setBrowseTree([
  {
    mediaId: 'recent',
    title: 'Recently Played',
    items: [...],
  },
  {
    mediaId: 'favorites',
    title: 'Favorites',
    items: [...],
  },
]);

CarPlay behavior

  • Tabs — Each top-level category appears as a tab. CarPlay supports a maximum of 4 tabs; if you provide more, a “More” tab is automatically created for the overflow.
  • Playback — Selecting an item starts playback natively without any extra handling in your app.
  • Now Playing — The Now Playing screen is provided automatically by CarPlay using the metadata you set via MPNowPlayingInfoCenter (RNTP keeps this up to date).
  • Item limits — The number of items displayed per list varies by car manufacturer and CarPlay version. Keep lists concise where possible.
ende