Merges its delta requirements into the android-auto-media base spec. Phases 2 (metadata/sort/filter/art) and 3 (subfolder scoping/shuffle) remain planned future work.
129 lines
7.4 KiB
Markdown
129 lines
7.4 KiB
Markdown
# Delta for Android Auto Media
|
|
|
|
Scope: Phase 1 (foundational plumbing) of `android-auto-local-music` only. See "Not in this delta" for explicit exclusions.
|
|
|
|
## ADDED Requirements
|
|
|
|
### Requirement: Local Music Root Access and Permission Persistence
|
|
|
|
The system MUST let the user select a local-music root folder via SAF (`ACTION_OPEN_DOCUMENT_TREE`), persist the granted URI permission, and MUST NOT crash or leave the local-music root folder in a broken state if that permission is later lost or was never granted.
|
|
|
|
#### Scenario: User picks a local music root folder
|
|
|
|
- GIVEN the user opens the local-music setup flow
|
|
- WHEN they complete the SAF folder picker and grant access
|
|
- THEN the selected folder's URI permission is persisted
|
|
- AND the local-music root becomes browsable in Android Auto without re-prompting the picker
|
|
|
|
#### Scenario: Persisted permission survives app restart
|
|
|
|
- GIVEN a local music root was previously picked and its permission persisted
|
|
- WHEN the app is restarted (cold start)
|
|
- THEN the local-music root remains browsable in Android Auto without re-prompting the SAF picker
|
|
|
|
#### Scenario: Permission revoked or never granted
|
|
|
|
- GIVEN the SAF permission for the local-music root was revoked outside the app, or no root was ever picked
|
|
- WHEN the local-music root folder is browsed in Android Auto
|
|
- THEN `getChildren` returns an empty or explanatory list, not an error
|
|
- AND the audio handler does not throw or crash
|
|
|
|
### Requirement: Local Music Browsable Tree
|
|
|
|
The Android Auto browse tree MUST expose a new non-playable local-music root folder alongside the existing station folders. Browsing that root and any nested subfolder MUST recursively mirror the picked SAF folder's structure: subfolders as non-playable `carpeta_local:<id>` items and audio files as playable `pista:<id>` items resolving to `PistaLocal` instances, using the raw filename as the displayed title.
|
|
|
|
#### Scenario: Car browses the local-music root
|
|
|
|
- GIVEN a local music root folder was picked and its permission is valid
|
|
- WHEN `getChildren` is called with the local-music root folder id
|
|
- THEN it returns the root's immediate subfolders as `carpeta_local:<id>` items and audio files as `pista:<id>` playable items
|
|
- AND each playable item's title is the file's raw filename
|
|
|
|
#### Scenario: Car browses a nested subfolder
|
|
|
|
- GIVEN a `carpeta_local:<id>` folder returned from a prior browse call
|
|
- WHEN `getChildren` is called with that folder id
|
|
- THEN it returns that subfolder's own contents (nested folders and/or tracks), recursively mirroring the on-device structure
|
|
- AND browsing an empty subfolder returns an empty list, not an error
|
|
|
|
### Requirement: Local Track Playback Reuses Existing Pipeline
|
|
|
|
Selecting a `pista:<id>` item MUST resolve to the corresponding `PistaLocal` and play it through the existing `playMediaItem`/`PluriWaveAudioHandler` pipeline, unchanged, including the shared EQ signal chain.
|
|
|
|
#### Scenario: User selects a local track
|
|
|
|
- GIVEN the user taps a `pista:<id>` playable item on the car head unit
|
|
- WHEN `playFromMediaId(id)` is called
|
|
- THEN the id resolves to a `PistaLocal` and playback starts through the existing `playMediaItem` internal path
|
|
- AND standard transport (play/pause/stop) and fallback album art work as they do for stations
|
|
|
|
#### Scenario: EQ still applies to local track playback (regression guard)
|
|
|
|
- GIVEN a non-flat EQ preset is currently the active/principal preset
|
|
- WHEN a `pista:<id>` item is played
|
|
- THEN the audible output passes through the same shared EQ signal chain used for station playback, with no separate or bypassed path for local tracks
|
|
|
|
#### Scenario: Unknown or stale track id
|
|
|
|
- GIVEN `playFromMediaId` is called with a `pista:<id>` that no longer resolves to a known `PistaLocal`
|
|
- WHEN resolution fails
|
|
- THEN playback does not start and no unhandled exception propagates from the handler
|
|
|
|
### Requirement: Local Music Folder Item Cap
|
|
|
|
A local-music folder (root or nested) whose immediate children exceed `_maxItemsPorCarpeta` (50) MUST be capped to that limit rather than returning unbounded results; pagination is out of scope for this delta.
|
|
|
|
#### Scenario: Folder exceeds the item cap
|
|
|
|
- GIVEN a picked or nested local-music folder contains more than 50 immediate children (subfolders and/or tracks combined)
|
|
- WHEN `getChildren` is called with that folder id
|
|
- THEN at most 50 items are returned, consistent with the existing `_maxItemsPorCarpeta` cap applied to station folders
|
|
- AND no error or crash occurs as a result of the truncation
|
|
|
|
## MODIFIED Requirements
|
|
|
|
### 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, Ecualizador, and the new local-music root) containing playable items. Playable station items SHOULD carry an audio-quality subtitle when known. The `Favoritos` folder additionally MAY contain non-playable favorite-group sub-folders; `Todas las emisoras` and `Mis emisoras` remain flat. The `Ecualizador` folder is flat, non-playable, and contains only the 6 fixed EQ preset items. The local-music root folder is non-playable and may itself be nested (see "Local Music Browsable Tree").
|
|
(Previously: root contained exactly four folders — Favoritos, Todas las emisoras, Mis emisoras, Ecualizador — with no local-music root.)
|
|
|
|
#### Scenario: Car requests the root
|
|
|
|
- GIVEN the car head unit connects and requests the root (`AudioService.browsableRootId`)
|
|
- WHEN `getChildren` is called with the root id
|
|
- THEN it returns five folder `MediaItem`s (Favoritos, Todas las emisoras, Mis emisoras, Ecualizador, and the local-music root), each with `playable: false`
|
|
|
|
#### Scenario: Car requests a folder with no stations
|
|
|
|
- GIVEN the user has zero favorite stations
|
|
- WHEN `getChildren` is 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 `getChildren` is 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.codec` and `Emisora.bitrate` are both known (non-null)
|
|
- WHEN it is mapped to a playable `MediaItem`
|
|
- THEN `displaySubtitle` SHALL contain a human-readable quality hint combining bitrate and codec
|
|
|
|
#### Scenario: Station has unknown codec or bitrate
|
|
|
|
- GIVEN a station's `Emisora.codec` or `Emisora.bitrate` (or both) is null/unknown
|
|
- WHEN it is mapped to a playable `MediaItem`
|
|
- THEN `displaySubtitle` SHALL omit the quality hint gracefully, and MUST NOT render literal placeholder text such as "null kbps"
|
|
|
|
#### Scenario: Ungrouped station appears exactly as before (regression guard)
|
|
|
|
- GIVEN a station's `Emisora.grupoFavoritosId` equals `GrupoFavoritos.sinAsignarId`
|
|
- WHEN the `Favoritos`, `Todas las emisoras`, or `Mis emisoras` folders are browsed
|
|
- THEN that station appears as a playable `emisora:<uuid>` item exactly as before, unaffected by the local-music root's existence or content
|
|
|
|
## Not in this delta
|
|
|
|
Phase 1 does NOT specify: local-track metadata display beyond the raw filename (no title/artist tagging), album art beyond the existing generic on-brand fallback, sort/filter of local tracks or folders, or shuffle. These are deferred to Phase 2/3 per the proposal and MUST NOT be implemented against this spec.
|