feat(auto): real metadata, quality sort and name buckets for local music [size:exception]

Local tracks now show embedded title/artist/album art (via native
MediaMetadataRetriever, cached through the existing FileProvider)
instead of the raw filename, falling back gracefully when a file
has no usable tags. Adds two navigable entry points per folder: sort
by audio quality (bitrate, capped at 150 tracks per folder to bound
worst-case latency) and alphabetical name buckets -- the closest
realistic form of "filtering" given Android Auto has no text-search
UI in this integration.

Metadata resolves only for the page actually being browsed (same
slice-cheap-then-map discipline as the paging change), backed by a
flat 256-entry LRU session cache that survives across pages. No new
permission, no new pub dependency, no l10n changes (car-tree labels
stay hardcoded Spanish, matching every existing label in the tree).
This commit is contained in:
2026-07-19 23:52:08 +02:00
parent e030a0975d
commit 352eb9fc37
13 changed files with 2470 additions and 82 deletions
@@ -0,0 +1,67 @@
# Proposal: Android Auto Local Music — Phase 2 (Real Metadata, Sort, Name Buckets)
## Intent
Phase 1 shipped browsable SAF folders with filename-only titles and generic placeholder art. Phase 2 delivers the deferred polish: real embedded metadata (title/artist/bitrate/sample-rate/album art), sort by name and audio quality, and name-based navigation — without breaking Phase 1's lazy paging philosophy or its pure-SAF, zero-dangerous-permission stance.
## Scope
### In Scope
- **A. Real metadata** — resolve embedded title/artist/bitrate/sample-rate + embedded album art for tracks on the page being shown/played, replacing filename title and `station_art_*` placeholder.
- **B. Sort** — name (filename, already cheap) and quality (bitrate desc, reusing the `ordenarEmisoras(..., OrdenEmisoras.calidad)` comparator shape) exposed as separate navigable sort-mode entries (Android Auto cannot re-sort in place).
- **C. Name navigation** — alphabetical navigable buckets (AF, etc.) as the realistic, browse-tree-native form of "filter by name".
- New translatable strings (sort-mode labels, "unknown metadata" text) added to ALL 13 ARB files from the start.
### Out of Scope
- Phase 3: subfolder scoping, shuffle, transport-control polish.
- Live text-search box (impossible under legacy MediaBrowserService).
- Voice search via `audio_service.onSearch` — flagged as Phase 3 candidate.
## Capabilities
### New Capabilities
- None.
### Modified Capabilities
- `local-music-browse`: gains metadata resolution, quality sort, alphabetical buckets (confirm exact spec name in `openspec/specs/` during sdd-spec).
- `android-auto-navigation`: local branch gains sort-mode + bucket entries.
## Approach — Metadata Extraction Decision (the one open technical call)
**DECISION: native `android.media.MediaMetadataRetriever` on the existing `pluriwave/file_actions` channel. No new pub dependency.**
Why, versus the Dart packages:
- Phase 1 tracks are `content://` SAF URIs (`buildDocumentUriUsingTree`), NOT file paths and NOT guaranteed MediaStore-indexed. `on_audio_query` is MediaStore-based and would (a) miss files outside standard media folders reached via an arbitrary SAF tree and (b) reintroduce `READ_MEDIA_AUDIO`, the dangerous permission Phase 1 deliberately rejected — REJECT.
- `audio_metadata_reader` / `flutter_media_metadata` need a `File`/bytes; SAF `content://` gives neither, so we'd still need native byte-streaming — more complexity, no less native code.
- `MediaMetadataRetriever.setDataSource(context, uri)` accepts a `content://` URI directly, returns TITLE/ARTIST/BITRATE + `getEmbeddedPicture()` bytes, reuses the existing `contentResolver`, adds zero deps and zero permissions. Consistent with the Phase 1 hand-rolled-channel ADR.
**DECISION: parse-on-demand + bounded in-memory session cache. NOT eager-scan-and-cache** — eager scan is slow, needs a persistence store, and goes stale when the user adds/removes files. On-demand is always fresh and matches paging. Design formalizes cache bounds/eviction.
Tension to formalize in sdd-design: **sort-by-quality needs bitrate for the whole folder**, so it must resolve metadata for all tracks in that folder (not just the visible page). Mitigation: name sort stays metadata-free (filename only); quality-sort cost is paid only when the user opts into that entry.
## Affected Areas
| Area | Impact | Description |
|------|--------|-------------|
| `MainActivity.kt` (`file_actions`) | Modified | New `readAudioMetadata(treeUri, documentId)` via MediaMetadataRetriever; embedded-art delivery (cache file + existing FileProvider) — static-review-only |
| `lib/modelos/pista_local.dart` | Modified | Add artist/bitrate/sampleRate/artUri fields |
| `lib/servicios/musica_local_auto.dart` | Modified | Metadata channel call + session cache |
| `lib/servicios/navegacion_auto.dart` | Modified | Metadata-backed title/subtitle/art; sort-mode + bucket entries |
| `lib/l10n/*.arb` (13) | Modified | Sort/metadata labels |
## Risks
| Risk | Likelihood | Mitigation |
|------|------------|------------|
| MMR sample-rate key is API 31+ | High | Degrade gracefully, reuse existing "unknown" handling |
| `artUri` needs a static URI, not bytes | High | Persist embedded art to cache dir, serve via existing FileProvider |
| Native untestable here (no build/DHU) | High | Static-review-only; keep sort/bucket/mapping logic pure Dart |
| Quality-sort forces full-folder parse | Med | Opt-in cost only when that entry is chosen |
| ARB drift across 13 locales | Med | All 13 in tasks from the start |
## Rollback Plan
Additive over Phase 1. Revert by removing `readAudioMetadata`, restoring filename title + placeholder art, and dropping sort-mode/bucket entries. Phase 1 browse/play untouched.
## Dependencies
- None new (native MMR, no pub package).
## Success Criteria
- [ ] Tracks show embedded title/artist and real album art when present, filename/placeholder fallback otherwise.
- [ ] Name and quality sort available as navigable entries; quality orders by bitrate desc.
- [ ] Alphabetical buckets navigable within a folder.
- [ ] Metadata resolved on-demand only; no eager scan, no persistence staleness.
- [ ] All 13 ARB files carry new strings; no regression to Phase 1 or stations; pure Dart unit-tested, native static-reviewed.