feat(auto): browsable Android Auto media tree with play-by-id [size:exception]
Expose PluriWave to Android Auto (projected) as a media app: - Declare car media support (automotive_app_desc.xml + manifest meta-data) so Android Auto discovers the existing MediaBrowserService. - New navegacion_auto.dart: ConstructorArbolAuto builds the browse tree (Favoritos / Todas las emisoras / Mis emisoras, 50-item cap, stable emisora:<id> media ids), reproducirPorMediaId routes a car tap to the existing playMediaItem pipeline, FuenteEmisorasAutoLocal serves the tree cold-start-safe (local favorites/custom stations before Flutter UI runs). - PluriWaveAudioHandler overrides getChildren/getMediaItem/playFromMediaId as thin delegations; playback pipeline untouched. - EstadoRadio pushes live station snapshots to the browse source and reconciles the selected station when playback starts from the car. - Every playable item ships title + artUri; stations without logo fall back to a bundled default art (android.resource://). Tests: 52/52 green (10 new navegacion_auto, 2 new estado_radio, plus audio safety-net suites). Handler overrides and native XML are static-review-only (no Android build env). Size exception approved for a single reviewable commit.
This commit is contained in:
@@ -16,6 +16,7 @@ import 'estado_busqueda.dart';
|
||||
import 'estado_ecualizador.dart';
|
||||
import 'estado_grabacion.dart';
|
||||
import 'orden_emisoras.dart';
|
||||
import '../servicios/navegacion_auto.dart';
|
||||
import '../servicios/persistencia_tolerante.dart';
|
||||
import '../servicios/servicio_audio.dart';
|
||||
import '../servicios/servicio_dispositivo_audio.dart';
|
||||
@@ -44,6 +45,7 @@ class EstadoRadio extends ChangeNotifier {
|
||||
ServicioGrabacionRadio? servicioGrabacion,
|
||||
SharedPreferences? prefs,
|
||||
Future<File> Function()? resolverArchivoCustom,
|
||||
FuenteEmisorasAuto? fuenteAuto,
|
||||
bool iniciarAutomaticamente = true,
|
||||
}) : audio = audio ?? ServicioAudio(),
|
||||
favoritos = favoritos ?? ServicioFavoritos(),
|
||||
@@ -52,7 +54,8 @@ class EstadoRadio extends ChangeNotifier {
|
||||
servicioEcualizador ?? ServicioEcualizador(prefs: prefs),
|
||||
_dispositivoAudio = dispositivoAudio,
|
||||
_prefs = prefs,
|
||||
_resolverArchivoCustom = resolverArchivoCustom {
|
||||
_resolverArchivoCustom = resolverArchivoCustom,
|
||||
_fuenteAuto = fuenteAuto {
|
||||
ecualizador = EstadoEcualizador(
|
||||
audio: this.audio,
|
||||
servicio: this.servicioEcualizador,
|
||||
@@ -96,6 +99,13 @@ class EstadoRadio extends ChangeNotifier {
|
||||
final SharedPreferences? _prefs;
|
||||
final Future<File> Function()? _resolverArchivoCustom;
|
||||
|
||||
/// Android Auto browse source (Design "live snapshot the source
|
||||
/// prefers"). Optional and unused by default — wired from main.dart via
|
||||
/// the [FuenteEmisorasAutoLocal] instance registered into the handler.
|
||||
/// When set, this instance's in-memory lists are pushed on every mutation
|
||||
/// so the car sees the same data as the phone without a duplicate read.
|
||||
final FuenteEmisorasAuto? _fuenteAuto;
|
||||
|
||||
/// Single startup instance injected from main() (S3-R4); falls back to
|
||||
/// getInstance() only when nothing was injected (tests, legacy callers).
|
||||
Future<SharedPreferences> _resolverPrefs() async =>
|
||||
@@ -292,6 +302,14 @@ class EstadoRadio extends ChangeNotifier {
|
||||
grabacion.activa) {
|
||||
unawaited(grabacion.detener());
|
||||
}
|
||||
// Design "playback coherence with EstadoRadio": a car-initiated
|
||||
// selection (Android Auto's playFromMediaId) changes audio.emisoraActual
|
||||
// directly, bypassing reproducir(). Without this, _emisoraSeleccionada
|
||||
// would keep shadowing the car's station on emisoraActual's getter.
|
||||
final actual = audio.emisoraActual;
|
||||
if (actual != null && actual.uuid != _emisoraSeleccionada?.uuid) {
|
||||
_emisoraSeleccionada = actual;
|
||||
}
|
||||
notifyListeners();
|
||||
});
|
||||
}
|
||||
@@ -311,6 +329,9 @@ class EstadoRadio extends ChangeNotifier {
|
||||
_errorCarga = _textos.radioApiConnectionError;
|
||||
} finally {
|
||||
_cargandoPopulares = false;
|
||||
// Design "live snapshot the source prefers": Android Auto's `Todas`
|
||||
// folder mirrors the same populares list the phone just loaded.
|
||||
_fuenteAuto?.actualizarSnapshot(todas: _populares);
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
@@ -318,6 +339,7 @@ class EstadoRadio extends ChangeNotifier {
|
||||
Future<void> cargarFavoritos() async {
|
||||
_listaFavoritos = await favoritos.obtenerTodos();
|
||||
await _normalizarEmisoraPreferida();
|
||||
_fuenteAuto?.actualizarSnapshot(favoritos: _listaFavoritos);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@@ -542,6 +564,7 @@ class EstadoRadio extends ChangeNotifier {
|
||||
detalle: 'resolucion de ruta',
|
||||
razon: e.toString(),
|
||||
);
|
||||
_fuenteAuto?.actualizarSnapshot(misEmisoras: _emisorasCustom);
|
||||
notifyListeners();
|
||||
return;
|
||||
}
|
||||
@@ -567,6 +590,7 @@ class EstadoRadio extends ChangeNotifier {
|
||||
razon: e.toString(),
|
||||
);
|
||||
}
|
||||
_fuenteAuto?.actualizarSnapshot(misEmisoras: _emisorasCustom);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@@ -582,6 +606,7 @@ class EstadoRadio extends ChangeNotifier {
|
||||
if (!await archivo.exists()) {
|
||||
_emisorasCustom = [];
|
||||
_customDegradado = false;
|
||||
_fuenteAuto?.actualizarSnapshot(misEmisoras: _emisorasCustom);
|
||||
notifyListeners();
|
||||
return null;
|
||||
}
|
||||
@@ -594,6 +619,7 @@ class EstadoRadio extends ChangeNotifier {
|
||||
detalle: archivo.path,
|
||||
razon: e.toString(),
|
||||
);
|
||||
_fuenteAuto?.actualizarSnapshot(misEmisoras: _emisorasCustom);
|
||||
notifyListeners();
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user