Phase 1: pick a device folder via SAF (persisted grant, no new permission), browse its nested subfolders/tracks as a 5th Android Auto root folder (hidden until configured), and play tracks through the existing pipeline (EQ, art rotation, cold-start-safe source). No metadata/sort/filter/shuffle yet -- filename is the title, generic rotating art is the placeholder; deferred to a follow-up phase. Adds a new pluriwave/file_actions native method (listAudioChildren) and an onActivityResult override in MainActivity for the SAF folder picker -- both static-review-only, no Android build available here.
5.2 KiB
Proposal: Android Auto Local Music — Phase 1 (Foundational Plumbing)
Intent
Users want to browse and play local music files (nested device folders) through Android Auto, mirroring the proven radio-station browse tree. This introduces THREE new domains at once — SAF/scoped-storage folder access, local audio metadata, and a new track model — so it MUST ship phased. This proposal scopes Phase 1 only: the foundational, independently shippable plumbing that gets a user-picked folder tree browsable and playable in the car, verified before any metadata/UX polish is layered on.
Scope
In Scope (Phase 1)
- SAF folder pick via
ACTION_OPEN_DOCUMENT_TREE, persisted URI permission, graceful revocation handling (mirror tolerant-read precedent). - Android storage permission declaration + request flow (
READ_MEDIA_AUDIO/ SAF grant), replacing the currently-empty manifest state. - Recursive
DocumentFiletree traversal enumerating audio files under the picked root (new dependency, e.g.shared_storage, or hand-rolled platform channel). - New
PistaLocalmodel (file URI, display name from filename, folder path) — NOT anEmisoraextension. - Android Auto browse tree extension: new root folder + folder/leaf
MediaItems via the existingConstructorArbolAuto/FuenteEmisorasAutopattern, id-prefix routing (pista:,carpeta_local:), respecting the_maxItemsPorCarpeta = 50cap. - Play-by-id + standard transport (play/pause/stop) by REUSING the existing
audio_service/PluriWaveAudioHandlerpipeline. - Album art: existing on-brand fallback rotation via
artUri(no embedded art yet).
Out of Scope (deferred)
- Phase 2: metadata extraction (title/artist/embedded art/bitrate/sample-rate), sort & filter by name/quality, embedded album art.
- Phase 3: subfolder scoping refinements, shuffle.
- Live/car-side waveform — architecturally impossible under legacy
MediaBrowserService(confirmed). Phone-side waveform reuse ofvisualizador_audio.dartis a separate future follow-up. - EQ — already shipped, direct reuse, no new work.
Capabilities
New Capabilities
local-music-browse: SAF folder access, persisted permission, recursive audio-file enumeration,PistaLocalmodel, and Android Auto browse/play of local files.
Modified Capabilities
android-auto-navigation: browse tree gains a local-music root folder alongside existing station folders (confirm exact spec name inopenspec/specs/during sdd-spec).
Approach
Extend the existing screaming-architecture seams rather than fork them. Recursive traversal + PistaLocal mapping are pure Dart (fully unit-testable). SAF platform-channel/permission/manifest work is static-review-only (no Android build/DHU in this env — same precedent as all prior native Android Auto work). Browse tree reuses ConstructorArbolAuto shape; playback reuses PluriWaveAudioHandler unchanged, handing a local file URI to just_audio exactly as station URLs are handed today.
Affected Areas
| Area | Impact | Description |
|---|---|---|
pubspec.yaml |
Modified | Add SAF/traversal dep; enable permission_handler |
android/app/src/main/AndroidManifest.xml |
Modified | Declare READ_MEDIA_AUDIO / SAF permission |
android/.../MainActivity.kt |
Modified | Extend platform channel for tree traversal (reuse file_actions pattern) |
lib/modelos/pista_local.dart |
New | PistaLocal model |
lib/servicios/ (folder source) |
New | SAF pick + persisted URI + recursive enumeration |
lib/servicios/navegacion_auto.dart |
Modified | Local-music root folder + pista:/carpeta_local: routing |
Risks
| Risk | Likelihood | Mitigation |
|---|---|---|
| SAF permission revoked outside app | Med | Tolerant reads; degrade to empty-but-valid tree (existing precedent) |
| Large nested trees exceed row caps | Med | Enforce _maxItemsPorCarpeta; defer pagination story to a later phase |
| SAF/native code not runtime-testable here | High | Static-review-only; isolate pure-Dart logic for full unit coverage |
| Scope creep pulling metadata/shuffle into Phase 1 | Med | Hard phase boundary; metadata is Phase 2 |
Rollback Plan
Additive change behind a new browse root. Revert by removing the local-music root folder registration + id-prefix routing in navegacion_auto.dart, the new model/service files, and the manifest/pubspec additions. Existing station browse/play is untouched, so rollback removes only new surface with no regression to shipped behavior.
Dependencies
- A SAF tree-traversal package (e.g.
shared_storage) OR a hand-rolledDocumentFileplatform channel — decide in sdd-design. permission_handlerre-enabled inpubspec.yaml.
Success Criteria
- User can pick a root folder; permission persists across app restarts.
- Nested audio files enumerate recursively into
PistaLocalinstances (unit-tested). - A local-music root folder appears in Android Auto with browsable subfolders and playable track leaves.
- Tapping a track plays it through the existing pipeline with working play/pause/stop and fallback album art.
- No regression to existing station browse/play; pure-Dart logic fully unit-tested, native code static-reviewed.