From cfea81813300635110c813ee7367764d5944f62c Mon Sep 17 00:00:00 2001 From: freetlab Date: Fri, 22 May 2026 19:40:02 +0200 Subject: [PATCH] fix(alarms): prevent overlapping playback --- lib/app.dart | 61 ++++++++++++++++++---- lib/estado/estado_alarmas.dart | 10 ++++ lib/pantallas/pantalla_alarma_sonando.dart | 14 ++++- 3 files changed, 72 insertions(+), 13 deletions(-) diff --git a/lib/app.dart b/lib/app.dart index ae89d56..420fb4f 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -63,6 +63,8 @@ class _PaginaPrincipalState extends State<_PaginaPrincipal> { StreamSubscription? _alarmaVencidaSubscription; EstadoRadio? _estadoSuscrito; bool _alarmaInicialProcesada = false; + bool _alarmaSonandoActiva = false; + String? _alarmaSonandoId; static const _paginas = [ PantallaInicio(), @@ -224,21 +226,58 @@ class _PaginaPrincipalState extends State<_PaginaPrincipal> { setState(() => _indice = 3); return; } - await Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => PantallaAlarmaSonando(alarma: alarma!), - fullscreenDialog: true, - ), - ); + await _mostrarAlarmaSonando(alarma); } Future _abrirAlarmaDirecta(AlarmaMusical alarma) async { - await Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => PantallaAlarmaSonando(alarma: alarma), - fullscreenDialog: true, - ), + await _mostrarAlarmaSonando(alarma); + } + + Future _mostrarAlarmaSonando(AlarmaMusical alarma) async { + final alarmas = context.read(); + alarmas.marcarEjecucionGestionada(alarma); + + if (_alarmaSonandoActiva) { + debugPrint( + '[PluriWave][alarmas] alarma ignorada porque ya hay una activa id=${alarma.id} activa=$_alarmaSonandoId', + ); + await alarmas.android.ocultarNotificacionAlarma(alarma.id); + return; + } + + _alarmaSonandoActiva = true; + _alarmaSonandoId = alarma.id; + + try { + await _prearrancarAudioAlarma(alarma); + if (!mounted) return; + await Navigator.of(context).push( + MaterialPageRoute( + builder: + (_) => PantallaAlarmaSonando( + alarma: alarma, + audioPrearrancado: alarma.emisora != null, + ), + fullscreenDialog: true, + ), + ); + } finally { + if (_alarmaSonandoId == alarma.id) { + _alarmaSonandoActiva = false; + _alarmaSonandoId = null; + } + } + } + + Future _prearrancarAudioAlarma(AlarmaMusical alarma) async { + final emisora = alarma.emisora; + if (emisora == null) return; + final radio = context.read(); + debugPrint( + '[PluriWave][alarmas] prearrancar emisora alarma id=${alarma.id} emisora=${emisora.nombre}', ); + await radio.audio.setVolumen(alarma.volumen.clamp(0.0, 1.0)); + unawaited(radio.reproducir(emisora)); } void _mostrarTimerDialog(BuildContext context) { diff --git a/lib/estado/estado_alarmas.dart b/lib/estado/estado_alarmas.dart index 02bdc5c..b0c4807 100644 --- a/lib/estado/estado_alarmas.dart +++ b/lib/estado/estado_alarmas.dart @@ -96,6 +96,16 @@ class EstadoAlarmas extends ChangeNotifier { notifyListeners(); } + void marcarEjecucionGestionada(AlarmaMusical alarma) { + final proxima = alarma.proximaEjecucion; + if (proxima == null) return; + final key = '${alarma.id}:${proxima.millisecondsSinceEpoch}'; + _ejecucionesEmitidas.add(key); + debugPrint( + '[PluriWave][alarmas] ejecucion gestionada id=${alarma.id} proxima=${proxima.toIso8601String()}', + ); + } + Future eliminarAlarma(String id) async { debugPrint('[PluriWave][alarmas] eliminar id=$id'); final config = await servicio.eliminarAlarma(id); diff --git a/lib/pantallas/pantalla_alarma_sonando.dart b/lib/pantallas/pantalla_alarma_sonando.dart index b6cae5d..87a099b 100644 --- a/lib/pantallas/pantalla_alarma_sonando.dart +++ b/lib/pantallas/pantalla_alarma_sonando.dart @@ -11,9 +11,14 @@ import '../servicios/servicio_audio.dart'; import '../widgets/pluri_glass_surface.dart'; class PantallaAlarmaSonando extends StatefulWidget { - const PantallaAlarmaSonando({super.key, required this.alarma}); + const PantallaAlarmaSonando({ + super.key, + required this.alarma, + this.audioPrearrancado = false, + }); final AlarmaMusical alarma; + final bool audioPrearrancado; @override State createState() => _PantallaAlarmaSonandoState(); @@ -45,7 +50,9 @@ class _PantallaAlarmaSonandoState extends State { _radioIntentada = true; await radio.audio.setVolumen(widget.alarma.volumen.clamp(0.0, 1.0)); - unawaited(radio.reproducir(emisora)); + if (!widget.audioPrearrancado) { + unawaited(radio.reproducir(emisora)); + } _estadoSub = radio.estadoStream.listen((estado) { if (estado == EstadoReproduccion.reproduciendo && mounted) { @@ -59,6 +66,9 @@ class _PantallaAlarmaSonandoState extends State { _fallbackTimer = Timer(const Duration(seconds: 12), () { if (mounted) _iniciarFallback(); }); + if (widget.audioPrearrancado && radio.audio.estaSonando) { + _fallbackTimer?.cancel(); + } } Future _iniciarFallback() async {