Files
pluriwave/openspec/changes/android-auto-local-music-phase2/proposal.md
T
FreeTLab 352eb9fc37 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).
2026-07-19 23:52:08 +02:00

5.2 KiB
Raw Blame History

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.