# Media3 Migration + Android Auto Support

## Goals

1. Proper system media notification with metadata, album art, and transport controls (Play/Stop + Seek to Live)
2. Android Auto compatibility — app registers as a media source with a browsable station catalog

## Approach

Migrate from legacy `MediaSessionCompat` + manual `NotificationHelper` to **Media3 `MediaLibraryService`** with a thin `Player` adapter wrapping the existing custom audio engine. The custom audio pipeline (OkHttp → IcyParser → MediaCodec → AudioTrack) remains completely untouched.

## Architecture

```
RadioController ──intent──▶ RadioPlaybackService (MediaLibraryService)
                                    │
                                    ├── RadioPlayerAdapter (implements Player)
                                    │        └── delegates to AudioEngine (untouched)
                                    │
                                    ├── MediaLibrarySession
                                    │        ├── auto-publishes metadata/state/art to OS
                                    │        ├── auto-manages notification (Play/Stop/SeekLive)
                                    │        └── serves browse tree for Android Auto
                                    │
                                    └── AudioEngine (unchanged custom pipeline)
```

## RadioPlayerAdapter

A thin facade implementing `androidx.media3.common.Player` that maps the small radio-relevant command surface to existing logic. It does NOT produce or route audio.

### Supported commands

- `COMMAND_PLAY_PAUSE`
- `COMMAND_STOP`
- `COMMAND_GET_CURRENT_MEDIA_ITEM`
- `COMMAND_GET_MEDIA_ITEMS_METADATA`
- Custom `SessionCommand("SEEK_TO_LIVE")` — exposed as a skip-forward button

### State mapping

| PlaybackState (existing) | Media3 Player.STATE_* | isPlaying |
|---|---|---|
| Idle | STATE_IDLE | false |
| Connecting | STATE_BUFFERING | false |
| Playing | STATE_READY | true |
| Paused | STATE_READY | false |
| Reconnecting | STATE_BUFFERING | false |

### Command mapping

- `play()` → `RadioController.play(currentStation)`
- `stop()` → `RadioController.stop()`
- `pause()` → `RadioController.stop()` (radio streams don't pause)
- Metadata changes from ICY events update `MediaMetadata` on the adapter, which auto-propagates to notification/lockscreen/Auto/Bluetooth

## RadioPlaybackService Changes

Converts from `LifecycleService` to `MediaLibraryService`.

### Stays the same

- Intent-based ACTION_PLAY/STOP/PAUSE/SEEK_LIVE control flow
- AudioEngine lifecycle (create, start, stop, event collection)
- Wake lock + WiFi lock management
- Reconnect loop with backoff
- Connection span / listening session DB tracking
- StreamResolver URL resolution

### Changes

- `LifecycleService` → `MediaLibraryService`
- `MediaSessionCompat` → `MediaLibrarySession` (built with `RadioPlayerAdapter`)
- `onGetSession()` returns the `MediaLibrarySession`
- `NotificationHelper` deleted — Media3 auto-generates notifications from session state
- `startForeground()` calls removed — `MediaLibraryService` handles foreground lifecycle
- On ICY metadata change, adapter's `MediaMetadata` updates → auto-propagates everywhere
- Album art: `AlbumArtResolver` resolves artwork → set on `MediaMetadata.artworkUri`

### Notification layout (auto-managed)

- Compact: Play/Stop toggle + Seek to Live (custom command button)
- Expanded: same + album art + station name + track info
- Channel importance: LOW

## Android Auto Browse Tree

```
[ROOT]
  ├── Playlist: "SomaFM"
  │     ├── Groove Salad
  │     ├── Drone Zone
  │     └── ...
  ├── Playlist: "My Favorites"
  │     ├── Station A
  │     └── Station B
  └── Unsorted (if any stations have no playlist)
        ├── Station X
        └── Station Y
```

### Callbacks

- `onGetLibraryRoot()` → root MediaItem (browsable)
- `onGetChildren(rootId)` → playlists + unsorted folder (browsable MediaItems)
- `onGetChildren(playlistId)` → stations in playlist (playable MediaItems)
- `onGetChildren("unsorted")` → stations with no playlist
- `onSetMediaItem()` / `onAddMediaItems()` → parse mediaId, look up station, call `RadioController.play()`

### Media IDs

- Root: `"root"`
- Playlist folder: `"playlist:{id}"`
- Unsorted folder: `"unsorted"`
- Station item: `"station:{id}"`

### Data freshness

DAO `Flow` methods use `.first()` for snapshots. Updates visible on next navigation (acceptable for a radio catalog).

## Dependencies

### Add

```toml
media3 = "1.6.0"
media3-session = { group = "androidx.media3", name = "media3-session", version.ref = "media3" }
media3-common = { group = "androidx.media3", name = "media3-common", version.ref = "media3" }
```

### Remove

- `media-session` (`androidx.media:media:1.7.1`)

No `media3-exoplayer` needed — custom audio engine stays.

## Manifest Changes

```xml
<service
    android:name=".service.RadioPlaybackService"
    android:exported="true"
    android:foregroundServiceType="mediaPlayback">
    <intent-filter>
        <action android:name="androidx.media3.session.MediaLibraryService"/>
        <action android:name="android.media.browse.MediaBrowserService"/>
    </intent-filter>
</service>

<meta-data
    android:name="com.google.android.gms.car.application"
    android:resource="@xml/automotive_app" />
```

## New File: `res/xml/automotive_app.xml`

```xml
<?xml version="1.0" encoding="utf-8"?>
<automotiveApp>
    <uses name="media"/>
</automotiveApp>
```

## Files Affected

| File | Action |
|---|---|
| `libs.versions.toml` | Add media3 version + libraries, remove old media |
| `app/build.gradle.kts` | Swap dependencies |
| `AndroidManifest.xml` | Update service declaration, add Auto meta-data |
| `res/xml/automotive_app.xml` | New file |
| `RadioPlayerAdapter.kt` | New file — Player facade |
| `RadioPlaybackService.kt` | Rewrite to extend MediaLibraryService |
| `NotificationHelper.kt` | Delete |
| `RadioController.kt` | Minor — may need adapter reference for metadata updates |
| `RadioApplication.kt` | Minor — remove NotificationHelper if referenced |
