fix(alarms): prevent overlapping playback
Build & Deploy Pluriwave / Build APK + AAB release (push) Successful in 2m0s
Build & Deploy Pluriwave / Análisis de código (push) Successful in 24s

This commit is contained in:
2026-05-22 19:40:02 +02:00
parent bc27e7832d
commit cfea818133
3 changed files with 72 additions and 13 deletions
+50 -11
View File
@@ -63,6 +63,8 @@ class _PaginaPrincipalState extends State<_PaginaPrincipal> {
StreamSubscription<AlarmaMusical>? _alarmaVencidaSubscription; StreamSubscription<AlarmaMusical>? _alarmaVencidaSubscription;
EstadoRadio? _estadoSuscrito; EstadoRadio? _estadoSuscrito;
bool _alarmaInicialProcesada = false; bool _alarmaInicialProcesada = false;
bool _alarmaSonandoActiva = false;
String? _alarmaSonandoId;
static const _paginas = [ static const _paginas = [
PantallaInicio(), PantallaInicio(),
@@ -224,21 +226,58 @@ class _PaginaPrincipalState extends State<_PaginaPrincipal> {
setState(() => _indice = 3); setState(() => _indice = 3);
return; return;
} }
await Navigator.of(context).push( await _mostrarAlarmaSonando(alarma);
MaterialPageRoute<void>(
builder: (_) => PantallaAlarmaSonando(alarma: alarma!),
fullscreenDialog: true,
),
);
} }
Future<void> _abrirAlarmaDirecta(AlarmaMusical alarma) async { Future<void> _abrirAlarmaDirecta(AlarmaMusical alarma) async {
await Navigator.of(context).push( await _mostrarAlarmaSonando(alarma);
MaterialPageRoute<void>( }
builder: (_) => PantallaAlarmaSonando(alarma: alarma),
fullscreenDialog: true, Future<void> _mostrarAlarmaSonando(AlarmaMusical alarma) async {
), final alarmas = context.read<EstadoAlarmas>();
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<void>(
builder:
(_) => PantallaAlarmaSonando(
alarma: alarma,
audioPrearrancado: alarma.emisora != null,
),
fullscreenDialog: true,
),
);
} finally {
if (_alarmaSonandoId == alarma.id) {
_alarmaSonandoActiva = false;
_alarmaSonandoId = null;
}
}
}
Future<void> _prearrancarAudioAlarma(AlarmaMusical alarma) async {
final emisora = alarma.emisora;
if (emisora == null) return;
final radio = context.read<EstadoRadio>();
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) { void _mostrarTimerDialog(BuildContext context) {
+10
View File
@@ -96,6 +96,16 @@ class EstadoAlarmas extends ChangeNotifier {
notifyListeners(); 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<void> eliminarAlarma(String id) async { Future<void> eliminarAlarma(String id) async {
debugPrint('[PluriWave][alarmas] eliminar id=$id'); debugPrint('[PluriWave][alarmas] eliminar id=$id');
final config = await servicio.eliminarAlarma(id); final config = await servicio.eliminarAlarma(id);
+12 -2
View File
@@ -11,9 +11,14 @@ import '../servicios/servicio_audio.dart';
import '../widgets/pluri_glass_surface.dart'; import '../widgets/pluri_glass_surface.dart';
class PantallaAlarmaSonando extends StatefulWidget { 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 AlarmaMusical alarma;
final bool audioPrearrancado;
@override @override
State<PantallaAlarmaSonando> createState() => _PantallaAlarmaSonandoState(); State<PantallaAlarmaSonando> createState() => _PantallaAlarmaSonandoState();
@@ -45,7 +50,9 @@ class _PantallaAlarmaSonandoState extends State<PantallaAlarmaSonando> {
_radioIntentada = true; _radioIntentada = true;
await radio.audio.setVolumen(widget.alarma.volumen.clamp(0.0, 1.0)); 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) { _estadoSub = radio.estadoStream.listen((estado) {
if (estado == EstadoReproduccion.reproduciendo && mounted) { if (estado == EstadoReproduccion.reproduciendo && mounted) {
@@ -59,6 +66,9 @@ class _PantallaAlarmaSonandoState extends State<PantallaAlarmaSonando> {
_fallbackTimer = Timer(const Duration(seconds: 12), () { _fallbackTimer = Timer(const Duration(seconds: 12), () {
if (mounted) _iniciarFallback(); if (mounted) _iniciarFallback();
}); });
if (widget.audioPrearrancado && radio.audio.estaSonando) {
_fallbackTimer?.cancel();
}
} }
Future<void> _iniciarFallback() async { Future<void> _iniciarFallback() async {