Skip to content

feat(android): implement Android Auto media library integration#147

Open
jerrydevengineer wants to merge 3 commits intoghenry22:masterfrom
jerrydevengineer:feat/android-auto-integration
Open

feat(android): implement Android Auto media library integration#147
jerrydevengineer wants to merge 3 commits intoghenry22:masterfrom
jerrydevengineer:feat/android-auto-integration

Conversation

@jerrydevengineer
Copy link
Copy Markdown

Summary

Because the app relies on react-native-track-player (which wraps Google's Media3 audio engine), the integration acts as a custom API gateway bridging the car's native OS requests to the React Native JavaScript thread.

Architectural Overview

The system consists of three distinct layers communicating asynchronously:

  1. The Client (Android Auto): The car dashboard running com.google.android.projection.gearhead. It expects standard Android Media3 responses.
  2. The API Gateway (MusicService.kt): The top-level service exposed by react-native-track-player. It intercepts the car's connection attempts.
  3. The Data Bridge (SubstreamerAutoModule.kt): A custom Expo Native Module that suspends the native thread while asking the TypeScript engine to query the local SQLite Navidrome database.

Expo Build Pipeline

Because this is a managed Expo project, native Android files are not committed directly. Instead, the architecture relies on Config Plugins registered in app.json:

  • with-automotive-xml.js: Generates the mandatory automotive_app_desc.xml file and injects the required metadata into the Manifest, announcing to the Android OS that this is a valid automotive media app.
  • withAndroidAuto.js: Re-wires the AndroidManifest.xml intent filters so that the car talks to the react-native-track-player service instead of looking for a generic entry point.

How the Data Flow Works

Because the SQLite database lives in the React Native thread, but Android Auto demands immediate data on the native Android thread, we use Guava Futures to hold the connection open.

1. Initialization (onGetLibraryRoot)

When the car connects, it immediately calls onGetLibraryRoot.

  • The MusicService responds with a statically defined MediaItem (ID: "root").
  • Contract Rule: The root item must have MediaMetadata with isBrowsable = true and isPlayable = false, and it must contain a Title. If these are missing, Android Auto will disconnect with an onConnectFailed error.

2. Requesting the Menu (onGetChildren)

Once the root is accepted, the car calls onGetChildren("root").

  • If the ID is "root", the MusicService returns the top-level static folders (e.g., Playlists, Downloaded).
  • If the user clicks a folder, the car calls onGetChildren("<folder_id>").

3. Crossing the Bridge (Reflection)

Because react-native-track-player and substreamer-auto are isolated Gradle modules, they cannot directly reference each other at compile time.

  • The MusicService uses Java Runtime Reflection to dynamically locate SubstreamerAutoModule$Companion.
  • It creates a SettableFuture and passes it, along with the requested parentId, into the requestDataFromTS method.

4. The TypeScript Roundtrip

  • SubstreamerAutoModule stores the SettableFuture in a static map (pendingRequests).
  • It emits an event (onCarRequestedData) over the React Native bridge.
  • The frontend TypeScript listener catches the event, queries the local SQLite database for the relevant Navidrome tracks/albums, and formats them into a JSON string.
  • TypeScript calls the asynchronous native function provideChildrenData(parentId, jsonData).

5. Fulfilling the Future

  • The Kotlin module parses the JSON, translates it into a list of Media3 MediaItem objects.
  • It removes the SettableFuture from the pending map and calls future.set(LibraryResult.ofItemList(...)).
  • The MusicService receives the resolved future and hands the data to Android Auto, rendering the UI on the car screen.

Changes

  • integrated substream into Android Auto

Testing

  • npx tsc --noEmit passes
  • npx jest --no-coverage passes
  • New/updated tests added (if applicable)
  • Tested on iOS
  • Tested on Android

Related Issues

  • "Doesn't seem to be working right now" on fresh install:
    Android Auto blocks sideloaded (debug) apps by default. You must enable Unknown Sources in the Android Auto Developer Settings on the device/emulator.
  • Crashes on Launch (IllegalArgumentException):
    Expo's DevLauncher attempts to create the React context incorrectly when a background service wakes the app before the UI is launched. Testing the Android Auto flow requires compiling a production release APK (--release), which removes the DevLauncher tool.
  • Unresolved Reference / ClassNotFound during Build:
    Ensure the substreamer-auto module's build.gradle explicitly implements androidx.media3:media3-session and com.google.guava:guava.

@ghenry22
Copy link
Copy Markdown
Owner

ghenry22 commented May 7, 2026

Have you tried this out against a real car head unit? How did it work?

I have a very specific way that I want to implement carplay and android auto for this project. It will be a complete replacement of the react native track player built from the ground up with features like android auto and carplay in mind. This is super useful though and even if I don't merge the PR with this exact implementation I WILL use it as a reference source when working through the android auto integration into the new audio library.

@jerrydevengineer
Copy link
Copy Markdown
Author

Hi ghenry22,

That long-term vision makes complete architectural sense! Working through the Android Auto Media3 contracts definitely highlighted the limitations of trying to route custom data through the existing react-native-track-player gateway. Building a purpose-built audio foundation from the ground up is definitely the right call for supporting these features natively.

To answer your question about testing: Yes, I actually got to test it against a real physical head unit! I plugged my Samsung Galaxy A53 (running Android 16) into a 2025 Kia Sorento (running recent OEM infotainment updates (i guess, it's not my car)).

It worked beautifully. Once the native gateway accepted the root node and the isBrowsable metadata, the car's dashboard seamlessly rendered both the split-screen 'Coolwalk' layout and the full-screen media browsing view using the data pulled from the React Native thread.

The only major caveat I ran into during testing was that I had to compile a production --release sideload to bypass the Expo DevLauncher. When the car's background service tried to wake the React context before the app UI was fully launched, the DevLauncher would crash.

I'm really glad this PR will be a useful reference for the native Android bridge and the API requirements when you start building the new library.

If you ever need someone to test the Android Auto implementation on a real car display when you get to that phase, I’d be happy to plug my phone back into the Sorento and help you verify it if i could!

@ghenry22
Copy link
Copy Markdown
Owner

ghenry22 commented May 7, 2026

Oh that's' cool that it worked regardless of how I implement it! It shows that it's 100% possible. I originally picked up react-nativve-track-player because it had some of the auto ground work done and there was some really good discussion around how to implement that was super useful and interesting.

Thanks so much for creating this PR and I will definitely be putting out a call for android auto and carplay testers once I have a stable base set.

@jerrydevengineer
Copy link
Copy Markdown
Author

Awesome, looking forward to seeing the new audio base! Feel free to ping me on here whenever you need testers. Good luck with the rebuild!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants