feat(android): implement Android Auto media library integration#147
feat(android): implement Android Auto media library integration#147jerrydevengineer wants to merge 3 commits intoghenry22:masterfrom
Conversation
|
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. |
|
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! |
|
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. |
|
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! |
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:
com.google.android.projection.gearhead. It expects standard Android Media3 responses.MusicService.kt): The top-level service exposed byreact-native-track-player. It intercepts the car's connection attempts.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 mandatoryautomotive_app_desc.xmlfile 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 theAndroidManifest.xmlintent filters so that the car talks to thereact-native-track-playerservice 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.MusicServiceresponds with a statically definedMediaItem(ID: "root").MediaMetadatawithisBrowsable = trueandisPlayable = false, and it must contain aTitle. If these are missing, Android Auto will disconnect with anonConnectFailederror.2. Requesting the Menu (
onGetChildren)Once the root is accepted, the car calls
onGetChildren("root").MusicServicereturns the top-level static folders (e.g., Playlists, Downloaded).onGetChildren("<folder_id>").3. Crossing the Bridge (Reflection)
Because
react-native-track-playerandsubstreamer-autoare isolated Gradle modules, they cannot directly reference each other at compile time.MusicServiceuses Java Runtime Reflection to dynamically locateSubstreamerAutoModule$Companion.SettableFutureand passes it, along with the requestedparentId, into therequestDataFromTSmethod.4. The TypeScript Roundtrip
SubstreamerAutoModulestores theSettableFuturein a static map (pendingRequests).onCarRequestedData) over the React Native bridge.provideChildrenData(parentId, jsonData).5. Fulfilling the Future
MediaItemobjects.SettableFuturefrom the pending map and callsfuture.set(LibraryResult.ofItemList(...)).MusicServicereceives the resolved future and hands the data to Android Auto, rendering the UI on the car screen.Changes
Testing
npx tsc --noEmitpassesnpx jest --no-coveragepassesRelated Issues
Android Auto blocks sideloaded (debug) apps by default. You must enable Unknown Sources in the Android Auto Developer Settings on the device/emulator.
Expo's
DevLauncherattempts 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 theDevLaunchertool.Ensure the
substreamer-automodule'sbuild.gradleexplicitly implementsandroidx.media3:media3-sessionandcom.google.guava:guava.