React Native Android Auto media player module with customizable UI (New Architecture/TurboModules)
npm install react-native-sportscarA React Native module for Android Auto integration with customizable media player support for both audio and video content.
- ๐ต Audio & Video Support: Play songs and videos through Android Auto (โ ๏ธ Video support is experimental)
- ๐จ Fully Customizable UI: Configure layouts and media items from TypeScript
- ๐ Android Auto Integration: Native MediaBrowserServiceCompat implementation
- โก Real-time Controls: Play, pause, seek, speed control, and more
- ๐ฑ Callback System: React to playback state changes and media transitions (Nitro Modules)
- ๐ผ๏ธ Image Caching: Efficient artwork loading with Glide integration
- ๐ Nitro Modules: Built with the latest React Native architecture for better performance
> โ ๏ธ Important: Video content only plays in parked mode due to Android Auto platform restrictions, not module limitations. While driving, only audio from videos will play.
>
> ๐งช Experimental: Video support is currently experimental and may have limitations or issues. Audio playback is fully stable and recommended for production use.
``bash
npm install react-native-sportscar react-native-nitro-modules
Dependencies:
- React Native >= 0.60
- Android minSdkVersion >= 29
- Android Auto compatible device or DHU
Android Setup
#### 1. Update your
android/build.gradle (project level)`gradle
buildscript {
ext {
minSdkVersion = 29 // Required for Android Auto
compileSdkVersion = 34
targetSdkVersion = 34
}
}
`#### 2. Add to your
android/app/src/main/AndroidManifest.xml`xml
xmlns:tools="http://schemas.android.com/tools">
android:name="com.sportscar.service.AndroidAutoMediaService"
android:exported="true"
android:foregroundServiceType="mediaPlayback">
android:name="com.google.android.gms.car.application"
android:resource="@xml/automotive_app_desc" />
android:name="androidx.car.app.CarAppMetadataHolderService.CAR_HARDWARE_MANAGER"
android:value="androidx.car.app.hardware.ProjectedCarHardwareManager"
tools:replace="android:value" />
`#### 3. Create
android/app/src/main/res/xml/automotive_app_desc.xml`xml
`Usage
$3
The library provides custom React hooks for easier state management:
#### usePlaybackStateChange Hook
A simple hook for tracking Android Auto playback state:
`typescript
import { usePlaybackStateChange } from 'react-native-sportscar';const MyComponent = () => {
const {
playbackInfo,
isLoading,
isPlaying,
isStopped,
isBuffering,
refresh,
} = usePlaybackStateChange({
fetchInitialState: true,
onStateChange: (info) => {
console.log('Playback state changed:', info.state);
},
});
return (
State: {playbackInfo?.state}
Playing: {isPlaying ? 'Yes' : 'No'}
Stopped: {isStopped ? 'Yes' : 'No'}
Buffering: {isBuffering ? 'Yes' : 'No'}
);
};
`$3
`typescript
import AndroidAuto from 'react-native-sports-car';
import type {
MediaLibrary,
AndroidAutoMediaItem,
} from 'react-native-sports-car';// Define your media library
const mediaLibrary: MediaLibrary = {
layoutType: 'GRID',
items: [
{
id: 'song1',
title: 'Bohemian Rhapsody',
artist: 'Queen',
album: 'A Night at the Opera',
duration: 355000, // in milliseconds
mediaType: 'AUDIO',
mediaUrl: 'https://example.com/bohemian-rhapsody.mp3',
artworkUrl: 'https://example.com/queen-album-art.jpg',
genre: 'Rock',
year: 1975,
},
{
id: 'video1',
title: 'Music Video',
artist: 'Artist Name',
duration: 240000,
mediaType: 'VIDEO',
mediaUrl: 'https://example.com/music-video.mp4',
artworkUrl: 'https://example.com/video-thumbnail.jpg',
},
],
};
// Initialize the media library
await AndroidAuto.initializeMediaLibrary(JSON.stringify(mediaLibrary));
`$3
`typescript
// Play media
await AndroidAuto.playMedia('song1');// Control playback
await AndroidAuto.pause();
await AndroidAuto.resume();
await AndroidAuto.stop();
// Next/Previous tracks
await AndroidAuto.skipToNext();
await AndroidAuto.skipToPrevious();
// Seek to position (in milliseconds)
await AndroidAuto.seekTo(120000); // Seek to 2 minutes
// Set playback speed
await AndroidAuto.setPlaybackSpeed(1.5); // 1.5x speed
// Get current playback state
const playbackInfo = await AndroidAuto.getPlaybackState();
console.log('Current position:', playbackInfo.currentPosition);
console.log('Is playing:', playbackInfo.isPlaying);
`$3
Since Nitro Modules don't support event listeners, the library uses callback-based event handling:
`typescript
import { useEffect } from 'react';useEffect(() => {
// Set up playback state callback
AndroidAuto.setPlaybackStateCallback((playbackInfo) => {
console.log('Playback state:', playbackInfo.state);
console.log('Current position:', playbackInfo.positionMs);
console.log('Is playing:', playbackInfo.state === 'playing');
});
// Set up media player event callback
AndroidAuto.setMediaPlayerEventCallback((eventType, data) => {
switch (eventType) {
case 'mediaItemChanged':
console.log('Now playing:', data.mediaItem.title);
break;
case 'error':
console.error('Playback error:', data.error);
break;
case 'playbackStateChanged':
console.log('Playback state changed:', data.state);
break;
}
});
return () => {
// Clean up callbacks
AndroidAuto.setPlaybackStateCallback(null);
AndroidAuto.setMediaPlayerEventCallback(null);
};
}, []);
`$3
`typescript
import { createPlaylist, getNextTrack, getPreviousTrack } from 'react-native-sportscar';// Create playlist from all playable items
const playlist = createPlaylist(mediaLibrary, { includeAllItems: true });
// Navigate tracks manually
const nextTrackId = getNextTrack(playlist, currentTrackId, true); // with repeat
const prevTrackId = getPreviousTrack(playlist, currentTrackId, true);
if (nextTrackId) {
await AndroidAuto.playMedia(nextTrackId);
}
`$3
`typescript
// Update media library with new content
const updatedLibrary: MediaLibrary = {
layoutType: 'LIST',
items: [...existingItems, ...newItems],
};await AndroidAuto.updateMediaLibrary(JSON.stringify(updatedLibrary));
`$3
`typescript
// Grid layout (default)
const gridLibrary: MediaLibrary = {
layoutType: 'GRID',
items: mediaItems,
};// List layout
const listLibrary: MediaLibrary = {
layoutType: 'LIST',
items: mediaItems,
};
// Set layout type separately
await AndroidAuto.setLayoutType('GRID');
`API Reference
$3
| Method | Parameters | Return Type | Description |
| ----------------------------- | -------------------------------------------- | -------------------------------------- | ------------------------------- |
|
initializeMediaLibrary | jsonString: string | Promise | Initialize media library |
| updateMediaLibrary | jsonString: string | Promise | Update media library |
| getMediaLibrary | - | Promise | Get current media library |
| playMedia | mediaId: string | Promise | Play specific media item |
| pause | - | Promise | Pause playback |
| resume | - | Promise | Resume playback |
| stop | - | Promise | Stop playback |
| skipToNext | - | Promise | Skip to next track |
| skipToPrevious | - | Promise | Skip to previous track |
| seekTo | positionMs: number | Promise | Seek to position |
| setPlaybackSpeed | speed: number | Promise | Set playback speed |
| getPlaybackState | - | Promise | Get current state |
| setLayoutType | layoutType: string | Promise | Set UI layout |
| refreshAndroidAutoUI | - | Promise | Refresh Android Auto UI |
| isCurrentlyPlaying | - | Promise | Check if currently playing |
| getLastPlayedMediaInfo | - | Promise | Get last played media info |
| handleAppStateChange | appState: string | Promise | Handle app state change |
| getCurrentAppState | - | Promise | Get current app state |
| setPlaybackStateCallback | callback: PlaybackStateCallback \| null | void | Set playback state callback |
| setMediaPlayerEventCallback | callback: MediaPlayerEventCallback \| null | void | Set media player event callback |$3
| Callback | Parameters | Description |
| -------------------------- | ------------------------------------------------------- | ---------------------------------- |
|
PlaybackStateCallback | (playbackInfo: PlaybackInfo) => void | Called when playback state changes |
| MediaPlayerEventCallback | (eventType: MediaPlayerEventType, data?: any) => void | Called for media player events |$3
| Event Type | Description |
| ---------------------- | ---------------------- |
|
playbackStateChanged | Playback state updated |
| mediaItemChanged | Media item changed |
| error | Error occurred |$3
`typescript
// Media Types
export type LayoutType = 'grid' | 'list';
export type MediaType = 'audio' | 'video' | 'folder';
export type PlaybackState =
| 'playing'
| 'paused'
| 'stopped'
| 'buffering'
| 'error';
export type AppState = 'foreground' | 'background' | 'destroyed';
export type RepeatMode = 'none' | 'one' | 'all';// Media Item Interface
interface MediaItem {
id: string;
title: string;
artist?: string;
album?: string;
duration?: number; // milliseconds
mediaType: MediaType;
mediaUrl: string;
artworkUrl?: string;
genre?: string;
year?: number;
children?: MediaItem[]; // for folders
}
// Media Library Interface
interface MediaLibrary {
layoutType: LayoutType;
items: MediaItem[];
}
// Playback Information
interface PlaybackInfo {
state: PlaybackState;
currentMediaId?: string;
positionMs: number; // milliseconds
durationMs: number; // milliseconds
playbackSpeed: number;
shuffleEnabled: boolean;
repeatMode: RepeatMode;
}
// Last Played Media Info
interface LastPlayedMediaInfo {
mediaId: string;
positionMs: number; // milliseconds
}
// Callback Types
type PlaybackStateCallback = (playbackInfo: PlaybackInfo) => void;
type MediaPlayerEventCallback = (
eventType: MediaPlayerEventType,
data?: any
) => void;
type MediaPlayerEventType =
| 'playbackStateChanged'
| 'mediaItemChanged'
| 'error';
`Testing with Android Auto
$3
1. Install Android Auto DHU:
`bash
# Download from Android Developer site
# Extract and run
./desktop-head-unit
`2. Enable Developer Mode:
- Install Android Auto app on your phone
- Tap version number 10 times to enable developer mode
- Enable "Developer settings" and "Unknown sources"
3. Connect via USB:
- Connect phone to computer via USB
- Enable USB debugging
- Launch your app and DHU
4. Enable Parked Mode (REQUIRED for video playback):
- In DHU, go to Settings โ Developer settings
- Enable "Simulate parked mode"
- Or use ADB:
adb shell settings put global android_auto_parked 1
- Note: Without parked mode, videos will only play audio$3
1. Connect phone to car's Android Auto system
2. Launch your app
3. Navigate to Media section in Android Auto interface
Video Playback Notes
> ๐จ Critical: Video content ONLY plays in parked mode due to Android Auto platform safety restrictions.
>
> ๐งช Experimental Feature: Video support is currently experimental and may not work reliably in all scenarios.
- While Driving: Only audio from video files will play (video shows as audio track)
- Parked Mode: Full video playback with visual content available
- Platform Limitation: This is enforced by Android Auto itself, NOT by this module
- Universal Behavior: All Android Auto media apps have this same restriction
- Experimental Status: Video functionality may have bugs, performance issues, or compatibility problems
- Production Recommendation: Use audio-only content for production apps until video support stabilizes
- Metadata: Video files show enhanced metadata in Android Auto interface
- Format Support: Supports standard video formats (MP4, WebM, etc.)
$3
Desktop Head Unit (DHU):
`bash
Method 1: DHU Settings
Go to Settings โ Developer settings โ Enable "Simulate parked mode"
Method 2: ADB Command
adb shell settings put global android_auto_parked 1
`Physical Car:
- Video will automatically play when the car is in park
- No manual configuration needed
Troubleshooting
$3
1. Build Errors:
`bash
# Clean and rebuild
cd android && ./gradlew clean
cd .. && npx react-native run-android
`2. AndroidX Car App Dependency Conflicts:
`
Error: Attribute meta-data#androidx.car.app.CarAppMetadataHolderService.CAR_HARDWARE_MANAGER@value
is also present at [androidx.car.app:app-automotive:1.4.0]
` Solution:
- Add
xmlns:tools="http://schemas.android.com/tools" to your manifest
- Add the meta-data element with tools:replace="android:value" as shown in step 2 above
- If you have androidx.car.app:app-automotive in your dependencies, consider removing it if not needed
- ๐ Detailed Guide: See ANDROID_CONFLICT_RESOLUTION.md for comprehensive solutions3. Service Not Found:
- Verify service name in AndroidManifest.xml matches exactly
- Check that automotive_app_desc.xml exists
4. Video Not Playing While Driving:
- This is normal behavior - Android Auto restricts video playback while driving
- Only audio will play from video files for safety reasons
- Enable parked mode for testing:
adb shell settings put global android_auto_parked 15. Video Issues (Experimental Feature):
- Current Limitation: Video currently only plays audio due to MediaBrowserService constraints
- Video support is experimental and may have various issues
- Technical Issue: MediaBrowserServiceCompat doesn't provide video rendering surface
- Workaround: Consider using Android Auto Car App Library for full video support
- Try using audio-only content if experiencing problems
- Check Android Auto logs for video-specific errors
- Consider video as a beta feature not ready for production
6. Playback Issues:
- Ensure media URLs are accessible
- Check network permissions
- Verify audio focus is properly handled
7. Android Auto Not Detecting:
- Confirm minSdkVersion >= 29
- Verify all required permissions are added
- Check automotive app descriptor
$3
`bash
Check Android Auto service
adb shell dumpsys activity service CarServiceMonitor logs
adb logcat | grep -E "(AndroidAuto|MediaService|CAR\.)"Check parked mode status
adb shell settings get global android_auto_parked
``Contributions are welcome! Please read our contributing guidelines and submit pull requests to our repository.
MIT License - see LICENSE file for details.
For issues and questions:
- GitHub Issues: Create an issue
- Documentation: Full API docs
---
Made with โค๏ธ for the React Native community