Promotes the android-auto-media capability spec to openspec/specs/ and moves both completed changes into openspec/changes/archive/.
7.3 KiB
Android Auto Media Specification
Purpose
PluriWave MUST expose a browsable station tree and play-by-media-id interface to Android Auto (projected) via the existing PluriWaveAudioHandler (audio_service MediaBrowserService/MediaSession), so a driver can browse and play stations from the car head unit without rebuilding the audio pipeline.
Requirements
Requirement: Android Auto Discovery Declaration
The app MUST declare itself as a media app to Android Auto so the car head unit discovers it.
Scenario: Android Auto scans installed apps
- GIVEN PluriWave is installed on the phone
- WHEN Android Auto scans installed apps for car-app support
- THEN it finds
res/xml/automotive_app_desc.xmldeclaring<uses name="media"/> - AND the
AndroidManifest.xml<application>block declares<meta-data android:name="com.google.android.gms.car.application" android:resource="@xml/automotive_app_desc"/> - AND PluriWave appears in the car's media app list
Requirement: Browsable Media Tree
getChildren MUST return a browsable tree rooted at AudioService.browsableRootId, organized into non-playable folders (Favoritos, Todas las emisoras, Mis emisoras) containing playable station items. Playable station items SHOULD carry an audio-quality subtitle when known.
(Previously: no subtitle requirement; folders and playable items were otherwise unchanged.)
Scenario: Car requests the root
- GIVEN the car head unit connects and requests the root (
AudioService.browsableRootId) - WHEN
getChildrenis called with the root id - THEN it returns three folder
MediaItems (Favoritos, Todas las emisoras, Mis emisoras), each withplayable: false
Scenario: Car requests a folder with no stations
- GIVEN the user has zero favorite stations
- WHEN
getChildrenis called with the Favoritos folder id - THEN it returns an empty list, not an error
Scenario: Browse requested before app state is loaded
- GIVEN the audio handler starts cold and station/favorites Provider state has not finished loading
- WHEN
getChildrenis called (root or any folder) - THEN it returns a valid, possibly empty, list without throwing and without blocking or crashing the service
Scenario: Station has known codec and bitrate
- GIVEN a station's
Emisora.codecandEmisora.bitrateare both known (non-null) - WHEN it is mapped to a playable
MediaItem - THEN
displaySubtitleSHALL contain a human-readable quality hint combining bitrate and codec (e.g. "128 kbps · MP3")
Scenario: Station has unknown codec or bitrate
- GIVEN a station's
Emisora.codecorEmisora.bitrate(or both) is null/unknown - WHEN it is mapped to a playable
MediaItem - THEN
displaySubtitleSHALL omit the quality hint gracefully (no subtitle, or a subtitle with no quality fragment) - AND the subtitle MUST NOT render literal placeholder text such as "null kbps" or "null · null"
Requirement: Playable Item Metadata
Every playable MediaItem (station) MUST include a non-empty title and a loadable artUri. Stations without a logo MUST fall back to on-brand artwork, and stations whose logo URL cannot actually be loaded MUST degrade to the same on-brand fallback instead of rendering broken or blank art. The fallback MUST be visually consistent with the phone UI's per-station rotation rather than a generic launcher-icon copy.
(Previously: fallback only covered a null/empty favicon and pointed at a bundled default-artwork asset that read as a copy of the launcher icon.)
Scenario: Station has a valid, reachable remote logo
- GIVEN a station has a remote logo URL that resolves to a loadable image
- WHEN it is mapped to a
MediaItem - THEN
titleis the station name andartUriis the station's logo URL
Scenario: Station has no logo (Case A)
- GIVEN a station has no logo (
faviconis null or empty) - WHEN it is mapped to a
MediaItem - THEN
artUriis set to one of the on-brandstation_art_*fallback assets instead of being empty or null
Scenario: Station's logo URL is present but unreachable (Case B)
- GIVEN a station has a non-empty
faviconURL that cannot be loaded (dead link, unreachable host, or non-image response) - WHEN it is mapped to (or resolved as) a
MediaItemin the Android Auto browse tree - THEN the system SHALL show the same on-brand fallback art used for Case A instead of broken or blank art
- AND the car head unit MUST NOT display an empty, broken-image, or indefinitely-loading art tile for that station
Scenario: Fallback art matches phone-UI per-station selection
- GIVEN a station falls back to on-brand art (Case A or Case B)
- WHEN the fallback asset is chosen for that station
- THEN the selected asset MUST be one of the same 4 rotating assets used by the phone UI (
station_art_aurora,station_art_cosmic,station_art_pulse,station_art_nova) - AND the same station MUST deterministically resolve to the same asset on both the phone UI and Android Auto (per-station selection parity), not a random or session-varying choice
Scenario: Fallback art is on-brand, not the launcher icon
- GIVEN a station requires fallback art (Case A or Case B)
- WHEN its
artUriis resolved - THEN it MUST NOT point at a byte-for-byte copy of the app launcher icon
- AND it MUST point at one of the 4 on-brand
station_art_*assets
Requirement: Media Item Resolution by ID
getMediaItem MUST resolve a single station id to its full MediaItem, returning null for unknown ids instead of throwing.
Scenario: Known station id
- GIVEN a valid station id that exists in current state
- WHEN
getMediaItem(id)is called - THEN it returns the corresponding
MediaItemwithtitleandartUripopulated
Scenario: Unknown station id
- GIVEN an id that does not match any known station or folder
- WHEN
getMediaItem(id)is called - THEN it returns
nullwithout throwing
Requirement: Play by Media ID Reuses Existing Playback Path
playFromMediaId MUST resolve the id to an Emisora and invoke the existing internal playMediaItem path; it MUST NOT duplicate playback or reconnection logic.
Scenario: User selects a station in the car
- GIVEN the user taps a playable station item on the car head unit
- WHEN
playFromMediaId(id)is called - THEN the id is resolved to an
Emisoravia the existing resolution logic (_emisoraDesdeMediaItem) - AND playback starts through the existing
playMediaIteminternal path - AND no separate or duplicated playback logic is executed
Scenario: Unknown or stale media id
- GIVEN
playFromMediaIdis called with an id that no longer resolves to a known station - WHEN resolution fails
- THEN playback does not start and no unhandled exception propagates from the handler
Requirement: Playback State Synchronization
Play/pause/stop state changes MUST remain synchronized between the car head unit and the phone UI, regardless of which side initiated the change.
Scenario: User pauses from the car
- GIVEN a station is playing and projected to the car
- WHEN the user pauses from the car head unit
- THEN the phone UI reflects the paused state via the shared
MediaSession/PlaybackState
Scenario: User pauses from the phone
- GIVEN a station is playing and projected to the car
- WHEN the user pauses from the phone UI
- THEN the car head unit reflects the paused state via the same shared session