Files
pluriwave/openspec/changes/auto-media-art-quality/proposal.md
T
FreeTLab c193650cc4 fix(auto): fall back to brand art and surface quality on dead/missing favicons
Android Auto no longer copies the launcher icon as placeholder art; it
rotates through the same 4 on-brand station_art assets the phone UI
already uses, keyed by the same per-station hash for visual parity.
Malformed or unusable favicon URLs (including a Dart Uri quirk where
'http://' reports hasAuthority=true with an empty host) now fail the
validity gate instead of being handed to the OS media browser as-is.
Browsable items also show codec/bitrate as a subtitle when known.
2026-07-19 13:06:18 +02:00

69 lines
4.6 KiB
Markdown

# Proposal: Android Auto Media Art & Quality Polish
## Intent
Fast-follow to `android-auto-media`, closing 3 gaps that parent's verify-report flagged (PASS WITH WARNINGS) plus one requested capability. In the car head unit today: (1) stations whose favicon URL is present but dead render broken/blank art — Android Auto resolves `artUri` OUTSIDE the Flutter engine, so the phone's `CachedNetworkImage.errorWidget` fallback never runs (only empty-favicon Case A is handled); (2) the fallback art is a byte-for-byte copy of the launcher icon, reading as a bug; (3) browse items expose no audio-quality hint even though `Emisora.codec`/`bitrate` already exist. This picks up the "fallback artwork for stations without favicons" work deferred in engram #2290.
## Scope
### In Scope
- **A** — Degrade Android Auto art gracefully when favicon is present but unreachable (Case B), not only empty (Case A).
- **B** — Replace launcher-icon placeholder with the 4 on-brand `station_art_*` assets, reusing `_fallbackArtFor`'s deterministic per-station hash-by-uuid rotation for phone/car parity. Requires native Android drawable copies of the 4 PNGs (same `android.resource://` technique as existing `default_station_art.png`).
- **C** — Surface `codec` + `bitrate` (e.g. "128 kbps · MP3") via `MediaItem.displaySubtitle` on browse items; omit gracefully when unknown (never "null kbps").
- Strict-TDD Dart tests for rotation selection, subtitle formatting, and Case-B fallback path.
### Out of Scope
- Commissioning brand-new custom artwork (no image-gen tooling here, #2290; reuse existing approved assets — external follow-up may add custom art later).
- iOS CarPlay; voice search; phone-UI art changes (already correct).
- `flutter build`/`analyze`/`gen-l10n` (hang in this environment).
## Capabilities
### New Capabilities
- None.
### Modified Capabilities
- `android-auto-media`: browse-item requirements extended — artwork MUST degrade gracefully for both empty AND unreachable favicons using on-brand rotating placeholders, and playable items SHOULD carry an audio-quality subtitle.
## Approach
Reuse, don't rebuild — mirror the phone UI. Port `_fallbackArtFor`'s uuid-hash rotation into `navegacion_auto.dart` so `_artUriPara()` picks one of 4 native drawables. Add `displaySubtitle` in `itemEmisora()` from existing `codec`/`bitrate`. **Deferred to design.md — the real open question:** HOW to detect Case B (dead URL) with no Flutter image widget in the OS browser loop. Design must weigh proactive reachability check + cache (network cost, staleness, invalidation) vs. piggybacking the phone's `CachedNetworkImage` error signal (misses car-first-open stations) vs. documenting the gap as a known limitation this iteration.
## Affected Areas
| Area | Impact | Description |
|------|--------|-------------|
| `lib/servicios/navegacion_auto.dart` | Modified | `_artUriPara()` rotation + Case-B fallback; `itemEmisora()` subtitle |
| `android/app/src/main/res/drawable/station_art_*.png` | New | 4 native copies of brand assets |
| `android/app/src/main/res/drawable/default_station_art.png` | Removed/Kept | Retire launcher-icon copy (design confirms) |
| `test/` (Dart) | New | Rotation, subtitle, fallback tests |
## Risks
| Risk | Likelihood | Mitigation |
|------|------------|------------|
| Case-B detection adds unbounded network cost | Med | Design phase decides; may accept as documented gap |
| Native drawable / asset drift over time | Low | Comment linking copies to `assets/images/` source |
| Malformed codec/bitrate → ugly subtitle | Low | Format guard; omit when either is unknown |
## Split Judgment
Keep as ONE change: A+B+C all touch `navegacion_auto.dart` browse-item construction and share tests. If design finds Case-B reachability checking too costly/complex, split A into its own follow-up and ship B+C (pure, low-risk parity + subtitle) first.
## Rollback Plan
Additive. Revert PR: restore `_artUriPara()`, drop `displaySubtitle`, delete the 4 native drawables (and restore `default_station_art.png` if retired), remove new tests. Phone UI and audio pipeline untouched — zero residual state.
## Dependencies
- `audio_service ^0.18.18` (present; `MediaItem.displaySubtitle`).
- Existing `assets/images/station_art_*.png` (source for native copies).
## Success Criteria
- [ ] Unreachable favicon shows on-brand art in car, not broken/blank (or gap documented).
- [ ] Fallback uses rotating `station_art_*`, matching phone per-station selection.
- [ ] Launcher-icon-as-art no longer appears.
- [ ] Browse items show quality subtitle; unknown quality omits cleanly.
- [ ] Dart tests pass under `flutter test`.