Frontend IntegrationΒΆ
This page documents the client-side JavaScript changes required for Android Auto integration.
π― GoalsΒΆ
- Detect Android Auto environment accurately
- Request size-optimized covers based on platform
- Update MediaSession with proper artwork
- Maintain backward compatibility
π Modified FilesΒΆ
static/js/player/playerUtils.jsΒΆ
Changed: extractMetadataFromDOM() function
Purpose: Request size-appropriate covers based on platform
static/js/player/androidAuto.jsΒΆ
Changed: Entire file replaced Purpose: Fix detection and artwork handling
π§ Implementation DetailsΒΆ
1. Update Metadata ExtractionΒΆ
File: static/js/player/playerUtils.js
Find this function (around line 268):
Replace with:
/**
* Extract metadata from DOM track element with platform-optimized artwork sizes
* Uses the new size-optimized cover API endpoints
*/
export function extractMetadataFromDOM(trackElement) {
const iOS = detectiOS();
const android = detectAndroid();
const coverImg = trackElement.querySelector('.track-cover');
let artwork = [];
if (coverImg && coverImg.src) {
const mimeType = getMimeTypeFromUrl(coverImg.src);
// Extract slug from cover URL
// Example: /covers/artist_album.jpg -> artist_album
const coverUrl = new URL(coverImg.src, window.location.origin);
const coverPath = coverUrl.pathname;
const slug = coverPath.split('/').pop()
.replace('.jpg', '')
.replace(/_\d+x\d+$/, '');
// Build size-optimized URLs based on platform
if (iOS) {
// iOS prefers larger sizes for lock screen
artwork = [
{ src: `/covers/${slug}_512x512.jpg`, sizes: '512x512', type: mimeType },
{ src: `/covers/${slug}_256x256.jpg`, sizes: '256x256', type: mimeType },
{ src: `/covers/${slug}_192x192.jpg`, sizes: '192x192', type: mimeType }
];
} else if (android && android.isAndroidAuto) {
// Android Auto needs full spectrum
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 }
];
} else {
// Desktop - simple set
artwork = [
{ src: `/covers/${slug}_192x192.jpg`, sizes: '192x192', type: mimeType },
{ src: `/covers/${slug}_512x512.jpg`, sizes: '512x512', type: mimeType }
];
}
}
return {
title: trackElement.dataset.title || 'Unknown',
artist: trackElement.dataset.artist || 'Unknown Artist',
album: trackElement.dataset.album || '',
artwork: artwork
};
}
What changed:
- Extracts slug from cover URL
- Builds size-specific URLs based on platform detection
- Returns array of artwork objects with multiple sizes
2. Replace Android Auto ModuleΒΆ
File: static/js/player/androidAuto.js
Action: Replace entire file with the new version
Key changes in new version:
A. Fixed DetectionΒΆ
async detectAndroidAuto() {
const userAgent = navigator.userAgent.toLowerCase();
// Check for Android Automotive OS or Android Auto indicators
const hasAutomotiveUA = userAgent.includes('android') &&
(userAgent.includes('automotive') ||
userAgent.includes('car'));
// Check for fullscreen/standalone mode
const isFullscreen = window.matchMedia('(display-mode: fullscreen)').matches ||
window.matchMedia('(display-mode: standalone)').matches;
// Check for car-like display dimensions
const hasCarDimensions = window.screen.width >= 800 &&
window.screen.height >= 480 &&
window.screen.width / window.screen.height > 1.5;
// Android Auto WebView exposes specific APIs
const hasAndroidAutoAPI = typeof window.AndroidAuto !== 'undefined';
// Check if running in WebView
const isWebView = userAgent.includes('wv');
// Combine signals for reliable detection
this.isAndroidAuto = hasAndroidAutoAPI ||
(hasAutomotiveUA && isFullscreen) ||
(isWebView && isFullscreen && hasCarDimensions);
return this.isAndroidAuto;
}
Why this fixes the bug:
- β Removed
getInstalledRelatedApps()- caused false positives - β Added specific automotive indicators
- β Requires multiple signals to match
- β Validates display characteristics
B. Fixed Artwork HandlingΒΆ
prepareArtwork(artworkArray) {
if (!artworkArray || artworkArray.length === 0) {
return this.getFallbackArtwork();
}
// Preserve the size-specific URLs from the backend
// No longer duplicates a single URL
return artworkArray.map(art => ({
src: art.src, // Keeps size-optimized URL
sizes: art.sizes, // Keeps declared size
type: art.type || 'image/jpeg'
}));
}
Why this is important:
- β Old code duplicated one URL for all sizes
- β New code preserves each size-specific URL
- β Browser/Android Auto picks best size automatically
π Integration FlowΒΆ
sequenceDiagram
participant User
participant PlayerControls
participant PlayerUtils
participant AndroidAuto
participant Flask
User->>PlayerControls: Click track
PlayerControls->>PlayerUtils: extractMetadataFromDOM()
PlayerUtils->>PlayerUtils: detectAndroid()
alt Is Android Auto
PlayerUtils->>PlayerUtils: Build 5 size URLs
PlayerUtils-->>PlayerControls: metadata with 5 artwork entries
else Is iOS
PlayerUtils->>PlayerUtils: Build 3 size URLs
PlayerUtils-->>PlayerControls: metadata with 3 artwork entries
else Is Desktop
PlayerUtils->>PlayerUtils: Build 2 size URLs
PlayerUtils-->>PlayerControls: metadata with 2 artwork entries
end
PlayerControls->>AndroidAuto: updateMediaMetadata(metadata)
AndroidAuto->>AndroidAuto: prepareArtwork()
AndroidAuto->>Navigator: Update MediaSession
Navigator->>Flask: GET /covers/slug_256x256.jpg
Flask-->>Navigator: 200 OK (40KB)
Navigator->>User: Display artwork in Android Auto
π Platform-Specific ArtworkΒΆ
Android AutoΒΆ
Sizes requested: 96, 128, 192, 256, 512 px
artwork = [
{ src: '/covers/slug_96x96.jpg', sizes: '96x96', type: 'image/jpeg' },
{ src: '/covers/slug_128x128.jpg', sizes: '128x128', type: 'image/jpeg' },
{ src: '/covers/slug_192x192.jpg', sizes: '192x192', type: 'image/jpeg' },
{ src: '/covers/slug_256x256.jpg', sizes: '256x256', type: 'image/jpeg' },
{ src: '/covers/slug_512x512.jpg', sizes: '512x512', type: 'image/jpeg' }
]
Android Auto typically chooses: 256Γ256 (optimal balance)
iOSΒΆ
Sizes requested: 192, 256, 512 px
artwork = [
{ src: '/covers/slug_512x512.jpg', sizes: '512x512', type: 'image/jpeg' },
{ src: '/covers/slug_256x256.jpg', sizes: '256x256', type: 'image/jpeg' },
{ src: '/covers/slug_192x192.jpg', sizes: '192x192', type: 'image/jpeg' }
]
iOS typically chooses: 512Γ512 (lock screen prefers larger)
DesktopΒΆ
Sizes requested: 192, 512 px
artwork = [
{ src: '/covers/slug_192x192.jpg', sizes: '192x192', type: 'image/jpeg' },
{ src: '/covers/slug_512x512.jpg', sizes: '512x512', type: 'image/jpeg' }
]
Desktop typically chooses: 512Γ512 (sufficient for most displays)
π§ͺ Testing in BrowserΒΆ
Test DetectionΒΆ
Open browser console and run:
// Test Android Auto detection
import { detectAndroid } from './playerUtils.js';
const android = detectAndroid();
console.log('Android Auto:', android?.isAndroidAuto);
// Test metadata extraction
const trackEl = document.querySelector('.track-item');
const metadata = extractMetadataFromDOM(trackEl);
console.log('Artwork sizes:', metadata.artwork.map(a => a.sizes));
Expected OutputΒΆ
On Android Auto:
{
isAndroid: true,
version: 11,
isAndroidAuto: true,
artwork: [
{ sizes: '96x96', ... },
{ sizes: '128x128', ... },
{ sizes: '192x192', ... },
{ sizes: '256x256', ... },
{ sizes: '512x512', ... }
]
}
On Regular Mobile Chrome:
{
isAndroid: true,
version: 119,
isAndroidAuto: false, // β Should be false!
artwork: [
{ sizes: '192x192', ... },
{ sizes: '512x512', ... }
]
}
π Backward CompatibilityΒΆ
Existing Code UnaffectedΒΆ
β
playerControls.js - No changes required
β
chromecast.js - No changes required
β
HTML templates - No changes required
β
CSS styles - No changes required
Progressive EnhancementΒΆ
- Regular browsers get standard artwork
- Android Auto gets optimized artwork
- Fallback to main cover if variants missing
- No breaking changes to API
π TroubleshootingΒΆ
Issue: Wrong platform detectedΒΆ
Check: Console logs from detectAndroid() and detectiOS()
Solution: Verify user agent contains expected strings
Issue: All requests use same sizeΒΆ
Check: Network tab - are URLs different?
Solution: Verify extractMetadataFromDOM() builds proper URLs
Issue: Images not loadingΒΆ
Check: Browser console for 404 errors Solution: Ensure backend generates variants on first request
Issue: MediaSession not updatingΒΆ
Check: Does navigator.mediaSession exist?
Solution: Requires HTTPS or localhost for MediaSession API
π± Mobile TestingΒΆ
Test on Real Android DeviceΒΆ
- Connect device with USB debugging
- Open Chrome DevTools β Remote devices
- Inspect your app's page
- Check console logs and network requests
Test on iOS DeviceΒΆ
- Enable Web Inspector on iOS device
- Connect to Mac with Safari
- Develop β Device β Your page
- Check console for detection results
π¨ MediaSession IntegrationΒΆ
The new androidAuto.js properly integrates with MediaSession API:
updateMediaMetadata(metadata) {
if (!this.mediaSession) return;
try {
// Prepare size-optimized artwork array
const artwork = this.prepareArtwork(metadata.artwork);
// Create MediaMetadata object
this.currentMetadata = new MediaMetadata({
title: metadata.title || 'Unknown Title',
artist: metadata.artist || 'Unknown Artist',
album: metadata.album || 'Unknown Album',
artwork: artwork // Multiple sizes for browser to choose
});
this.mediaSession.metadata = this.currentMetadata;
} catch (error) {
console.error('Failed to update MediaSession metadata:', error);
}
}
What happens:
- Artwork array has multiple sizes
- Browser/Android Auto chooses best size
- Only chosen size is downloaded
- Bandwidth saved automatically
π Performance MonitoringΒΆ
Add performance tracking:
// In extractMetadataFromDOM()
const startTime = performance.now();
const metadata = extractMetadataFromDOM(trackElement);
const endTime = performance.now();
console.log(`Metadata extraction: ${endTime - startTime}ms`);
// Track artwork load times
metadata.artwork.forEach(art => {
const img = new Image();
const loadStart = performance.now();
img.onload = () => {
const loadTime = performance.now() - loadStart;
console.log(`Loaded ${art.sizes}: ${loadTime}ms`);
};
img.src = art.src;
});
Next StepsΒΆ
π Continue to Testing Guide to verify your integration works correctly.