Android Auto Integrationยถ
Mixtape Society supports Android Auto for seamless in-car playback through the Media Session API, providing steering wheel controls, dashboard integration, and voice command support.
๐ฏ Overviewยถ
Android Auto integration enables:
- Dashboard integration - Control from car touchscreen
- Voice commands - "OK Google, pause", "next song"
- Steering wheel controls - Physical car buttons
- Safe driving mode - Simplified, distraction-free interface
- Full metadata display - Album art and track info on display
- Automatic connection - Works when phone connects to car
Key Differences from Chromecastยถ
| Feature | Android Auto | Chromecast |
|---|---|---|
| Network | Local playback | WiFi casting |
| Setup | Automatic | Manual device selection |
| Server Reqs | None | CORS + Range requests |
| API | Media Session only | Cast SDK + Media Session |
| Artwork | Platform-optimized | Single size |
| Seeking | Built-in | Requires range requests |
Android Auto is simpler - it's just enhanced Media Session API with no network complexity.
๐๏ธ Architectureยถ
System Overviewยถ
graph LR
User[User in Car]
Car[Car Dashboard]
Phone[Phone Browser]
PlayerControls[playerControls.js]
AndroidAuto[androidAuto.js]
MediaSession[Media Session API]
Audio[Audio Element]
User --> Car
Car --> Phone
Phone --> PlayerControls
PlayerControls --> AndroidAuto
AndroidAuto --> MediaSession
MediaSession --> Audio
style AndroidAuto fill:#4a6fa5
style MediaSession fill:#c65d5d
style Car fill:#4a8c5f
Component Interactionยถ
sequenceDiagram
actor User
participant PlayerControls as playerControls.js
participant PlayerUtils as playerUtils.js
participant AndroidAuto as androidAuto.js
participant Audio as main_player
User->>PlayerControls: initPlayerControls()
PlayerControls->>PlayerUtils: detectiOS()
PlayerControls->>PlayerUtils: detectAndroid()
PlayerControls->>PlayerUtils: logDeviceInfo()
alt Android device
PlayerControls->>AndroidAuto: logAndroidAutoStatus()
end
User->>PlayerControls: tap track item
PlayerControls->>PlayerControls: playTrack(index)
alt Casting active
PlayerControls->>PlayerControls: castJumpToTrack(index)
PlayerControls->>PlayerControls: return (Chromecast handles Media Session)
else Local playback
PlayerControls->>Audio: set src and play()
PlayerControls->>PlayerUtils: extractMetadataFromDOM(trackElement)
PlayerUtils-->>PlayerControls: metadata (title, artist, album, artwork)
alt AndroidAuto connected
PlayerControls->>AndroidAuto: isAndroidAutoConnected()
AndroidAuto-->>PlayerControls: true
PlayerControls->>AndroidAuto: setupAndroidAutoMediaSession(metadata, playerControlsAPI, Audio)
AndroidAuto->>AndroidAuto: prepareArtwork(metadata.artwork)
AndroidAuto->>AndroidAuto: setupActionHandlers(playerControlsAPI, Audio)
AndroidAuto->>AndroidAuto: setupAudioEventListeners(Audio)
else Not AndroidAuto
PlayerControls->>AndroidAuto: isAndroidAutoConnected()
AndroidAuto-->>PlayerControls: false
PlayerControls->>PlayerUtils: setupLocalMediaSession(metadata, playerControlsAPI)
end
end
User->>PlayerControls: stop playback
alt AndroidAuto connected
PlayerControls->>AndroidAuto: clearAndroidAutoMediaSession()
else Not AndroidAuto
PlayerControls->>PlayerUtils: clearMediaSession()
end
Class Relationshipsยถ
classDiagram
class PlayerUtils {
+detectiOS()
+detectAndroid()
+logDeviceInfo()
+setupLocalMediaSession(metadata, playerControls)
+clearMediaSession()
+updateMediaSessionPlaybackState(state)
+updateMediaSessionPosition(position, duration, playbackRate)
+getMimeTypeFromUrl(url)
+extractMetadataFromDOM(trackElement)
}
class PlayerControls {
+initPlayerControls()
-checkCastingState()
-playTrack(index)
-updatePositionState()
-syncPlayIcons()
-togglePlayPause()
-silenceLocalPlayer()
-enableLocalPlayer()
-updateLocalMediaSession(metadata)
-currentIndex
-isCurrentlyCasting
-playerControlsAPI
}
class AndroidAutoModule {
+isAndroidAutoConnected()
+setupAndroidAutoMediaSession(metadata, playerControls, audioElement)
+clearAndroidAutoMediaSession()
+logAndroidAutoStatus()
-prepareArtwork(originalArtwork)
-setupActionHandlers(playerControls, audioElement)
-updatePositionState(audioElement)
-setupAudioEventListeners(audioElement)
-ANDROID_AUTO_ARTWORK_SIZES
}
class MediaSessionAPI {
+metadata
+playbackState
+setActionHandler(action, handler)
+setPositionState(state)
}
PlayerControls ..> PlayerUtils : uses
PlayerControls ..> AndroidAutoModule : uses
AndroidAutoModule ..> MediaSessionAPI : configures
PlayerUtils ..> MediaSessionAPI : setupLocalMediaSession()
๐ Quick Startยถ
Prerequisitesยถ
User Requirements:
- Android phone (Android 5.0+)
- Car with Android Auto support OR Android Auto app
- USB cable or wireless Android Auto
Developer Requirements:
- Media Session API support (already in Mixtape Society)
- Cover art optimization endpoints (already implemented)
Testing Connectionยถ
- Connect phone to car (USB or wireless)
- Open Mixtape Society in browser
- Play a mixtape
- Check car display - should show track info
- Test controls - steering wheel buttons, voice commands
Verificationยถ
Check browser console for:
๐ Android Auto Status:
Connected: Yes โ
Media Session API: Available โ
User Agent: ... Android ... automotive ...
๐ฆ Core Componentsยถ
1. Detection Moduleยถ
File: static/js/player/androidAuto.js
Detection logic:
export function isAndroidAutoConnected() {
const ua = navigator.userAgent.toLowerCase();
const isAndroidAuto = ua.includes('android') && (
ua.includes('vehicle') ||
ua.includes('automotive') ||
document.referrer.includes('android-auto')
);
const hasAutoAPIs = 'getInstalledRelatedApps' in navigator;
return isAndroidAuto || hasAutoAPIs;
}
User agent patterns:
- Contains "android"
- Contains "vehicle" OR "automotive"
- Referrer includes "android-auto"
2. Media Session Setupยถ
Enhanced for Android Auto:
export function setupAndroidAutoMediaSession(metadata, playerControls, audioElement) {
// 1. Set metadata with optimized artwork
const artwork = prepareArtwork(metadata.artwork);
navigator.mediaSession.metadata = new MediaMetadata({
title: metadata.title || 'Unknown Track',
artist: metadata.artist || 'Unknown Artist',
album: metadata.album || 'Mixtape',
artwork: artwork // Multiple sizes for Android Auto
});
// 2. Set playback state
navigator.mediaSession.playbackState = audioElement.paused ? 'paused' : 'playing';
// 3. Set ALL action handlers (including seek)
setupActionHandlers(playerControls, audioElement);
// 4. Set position state (CRITICAL for seeking)
updatePositionState(audioElement);
// 5. Listen for audio events
setupAudioEventListeners(audioElement);
}
3. Artwork Optimizationยถ
Android Auto requires multiple sizes:
const ANDROID_AUTO_ARTWORK_SIZES = [
{ size: '96x96', type: 'image/jpeg' }, // Required minimum
{ size: '128x128', type: 'image/jpeg' }, // Recommended
{ size: '192x192', type: 'image/jpeg' }, // Optimal
{ size: '256x256', type: 'image/jpeg' }, // High quality
{ size: '512x512', type: 'image/jpeg' } // Maximum
];
Platform detection in playerUtils.js:
export function extractMetadataFromDOM(trackElement) {
const android = detectAndroid();
if (android && android.isAndroidAuto) {
// Request all sizes for Android Auto
artwork = [
{ src: `/covers/${slug}_96x96.jpg`, sizes: '96x96', type: mimeType },
{ src: `/covers/${slug}_128x128.jpg`, sizes: '128x128', type: mimeType },
{ src: `/covers/${slug}_192x192.jpg`, sizes: '192x192', type: mimeType },
{ src: `/covers/${slug}_256x256.jpg`, sizes: '256x256', type: mimeType },
{ src: `/covers/${slug}_512x512.jpg`, sizes: '512x512', type: mimeType }
];
}
}
4. Action Handlersยถ
Complete set for Android Auto:
function setupActionHandlers(playerControls, audioElement) {
// Basic playback
navigator.mediaSession.setActionHandler('play', () => playerControls.play());
navigator.mediaSession.setActionHandler('pause', () => playerControls.pause());
navigator.mediaSession.setActionHandler('stop', () => {
playerControls.pause();
audioElement.currentTime = 0;
});
// Track navigation
navigator.mediaSession.setActionHandler('previoustrack', () => playerControls.previous());
navigator.mediaSession.setActionHandler('nexttrack', () => playerControls.next());
// Seeking (CRITICAL for Android Auto)
navigator.mediaSession.setActionHandler('seekto', (details) => {
if (details.seekTime !== undefined) {
audioElement.currentTime = details.seekTime;
updatePositionState(audioElement);
}
});
// Optional: Seek forward/backward (10 seconds)
navigator.mediaSession.setActionHandler('seekforward', () => {
audioElement.currentTime = Math.min(
audioElement.currentTime + 10,
audioElement.duration
);
});
navigator.mediaSession.setActionHandler('seekbackward', () => {
audioElement.currentTime = Math.max(
audioElement.currentTime - 10,
0
);
});
}
5. Position State Managementยถ
Updates every second during playback:
function updatePositionState(audioElement) {
if (!('setPositionState' in navigator.mediaSession)) return;
if (audioElement.duration && !isNaN(audioElement.duration)) {
try {
navigator.mediaSession.setPositionState({
duration: audioElement.duration,
playbackRate: audioElement.playbackRate,
position: Math.min(audioElement.currentTime, audioElement.duration)
});
} catch (error) {
console.warn('Could not set position state:', error);
}
}
}
// Update automatically during playback
let positionUpdateInterval;
audioElement.addEventListener('play', () => {
positionUpdateInterval = setInterval(() => {
updatePositionState(audioElement);
}, 1000); // Update every second
});
audioElement.addEventListener('pause', () => {
clearInterval(positionUpdateInterval);
});
๐จ How It Worksยถ
User Journeyยถ
- User connects phone to car
- USB cable or wireless Android Auto
-
Car recognizes Android Auto device
-
User opens Mixtape Society
- Browser detects Android Auto environment
-
isAndroidAutoConnected()returns true -
User plays a mixtape
setupAndroidAutoMediaSession()called- Enhanced metadata with multiple cover sizes
-
All action handlers configured
-
Track displays on car dashboard
- Cover art (optimal size selected)
- Track title and artist
-
Album name
-
User controls playback
- Steering wheel buttons โ action handlers
- Voice commands โ Media Session API
-
Touchscreen โ browser controls
-
Seeking works seamlessly
- Car scrubs timeline โ
seektohandler - Position updates every second
- Accurate progress display
Technical Flowยถ
Initialization:
Track Change:
playTrack(index)
โ extractMetadataFromDOM()
โ Detect Android Auto
โ Build artwork array (5 sizes)
โ setupAndroidAutoMediaSession()
โ Set metadata
โ Configure action handlers
โ Start position updates
User Interaction:
User presses steering wheel button
โ Car sends Media Session event
โ Action handler fires
โ Player responds (play/pause/next/etc)
โ UI updates
๐ง Backend Requirementsยถ
Cover Art Endpointsยถ
Required endpoints (already implemented):
GET /covers/{slug}_96x96.jpg
GET /covers/{slug}_128x128.jpg
GET /covers/{slug}_192x192.jpg
GET /covers/{slug}_256x256.jpg
GET /covers/{slug}_512x512.jpg
Implementation: See Backend Implementation Guide
No Special Server Requirementsยถ
Unlike Chromecast, Android Auto requires no server changes:
- โ No CORS headers needed (local playback)
- โ No range request handling needed
- โ No special MIME types
- โ Just standard audio streaming
The complexity is all client-side.
๐ฑ Platform Supportยถ
Android Auto (In-Car)ยถ
Full support:
- โ Dashboard integration
- โ Steering wheel controls
- โ Voice commands
- โ Position state and seeking
- โ Multiple cover art sizes
Requirements:
- Android 5.0+ phone
- Android Auto compatible car OR Android Auto app
- Media Session API support (all modern Android)
Android (Not in Car)ยถ
Standard Media Session:
- โ Notification controls
- โ Lock screen controls
- โ Basic metadata
- โ Single cover art size
Difference:
Android Auto detection returns false, so it falls back to standard Media Session setup via playerUtils.setupLocalMediaSession().
Other Platformsยถ
iOS, Desktop:
- Standard Media Session API
- No Android Auto enhancements
- Single cover art size
๐งช Testingยถ
For complete testing procedures, see the Testing Guide.
Quick checklist:
- Detection works (console shows "Android Auto Connected")
- Cover art displays on dashboard
- Metadata shows correctly (title, artist, album)
- Play/pause from steering wheel
- Next/previous track from controls
- Voice commands work ("OK Google, pause")
- Seeking from dashboard timeline
- Position updates in real-time
๐ Troubleshootingยถ
Detection Issuesยถ
Symptom: Android Auto not detected when connected
Check:
// In browser console:
navigator.userAgent
// Should contain "android" and ("vehicle" OR "automotive")
Solutions:
- Verify car is in Android Auto mode
- Check USB connection (or wireless pairing)
- Restart browser
- Check console for detection logs
Cover Art Not Showingยถ
Symptom: No album art on dashboard
Check network tab:
GET /covers/artist_album_96x96.jpg - Should return 200
GET /covers/artist_album_192x192.jpg - Should return 200
Solutions:
- Verify cover optimization is enabled
- Check cover endpoints return correct MIME type
- Ensure slugs match between frontend and backend
Seeking Not Workingยถ
Symptom: Timeline scrubbing doesn't work
Check:
- Position state being set:
navigator.mediaSession.setPositionState(...) - Duration is valid:
audioElement.durationis not NaN seektohandler is registered
Debug:
// Add to setupActionHandlers:
navigator.mediaSession.setActionHandler('seekto', (details) => {
console.log('๐ฏ Seek to:', details.seekTime);
audioElement.currentTime = details.seekTime;
});
๐ Implementation Guidesยถ
Detailed Documentationยถ
- Frontend Integration
- JavaScript code changes
- Platform detection
- Metadata extraction
-
Action handler setup
- Cover art optimization
- Size generation
-
Endpoint configuration
- Test environment setup
- Validation procedures
- Common test cases
- Debug logging
Related Technologiesยถ
- Chromecast Integration
- Alternative casting method
- Network-based playback
-
Different use case
- UI control coordination
-
State management
- Size optimization
- Image processing
๐ฏ Future Enhancementsยถ
Plannedยถ
- Lyrics sync - Display synchronized lyrics on dashboard
- Playlist editing - Reorder queue from car interface
- Enhanced voice - Natural language commands
Under Considerationยถ
- Multiple queues - Save different playlists for different trips
- Offline mode - Download mixtapes for offline in-car playback
- Car profile - Remember settings per vehicle
For implementation details, see the Frontend Integration Guide and Backend Implementation Guide.
