fix(alarm): defer the Dart fade-in until the native handoff confirms
Build & Deploy PluriWave / Análisis de código (push) Successful in 37s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 1m50s

The native service and the Flutter player each ran their own 5%-to-
target fade-in, and both could drive audible volume at the handoff,
producing a jump or ramp reset. The Dart ramp now starts exactly once
from the handoff-confirmation path: the player still pre-starts at 5%,
and _confirmarAudioFlutterListo() starts the ramp in a finally block
so it runs whether the native confirmation succeeds or fails — the
alarm can never stay stuck at 5% if the native side is already gone.

Work unit 3/3 of alarm-volume-ramp-restore (fade-in dedup).
This commit is contained in:
2026-07-11 09:57:06 +02:00
parent a6e1177752
commit 66a19525bd
4 changed files with 193 additions and 14 deletions
+19 -5
View File
@@ -64,7 +64,6 @@ class _PantallaAlarmaSonandoState extends State<PantallaAlarmaSonando> {
if (!widget.audioPrearrancado) {
unawaited(radio.reproducir(emisora));
}
_iniciarFadeIn();
// S7-R4 boundary: only `reproduciendo` cancels the fallback timer —
// `reconectando`/`cargando` do NOT count as playing, so the 12-second
@@ -94,7 +93,6 @@ class _PantallaAlarmaSonandoState extends State<PantallaAlarmaSonando> {
_fallbackActivo = true;
await _fallbackPlayer.setAsset(_assetFallback(widget.alarma.sonidoInterno));
await _fallbackPlayer.play();
_iniciarFadeIn();
await _confirmarAudioFlutterListo();
if (mounted) setState(() {});
}
@@ -131,12 +129,28 @@ class _PantallaAlarmaSonandoState extends State<PantallaAlarmaSonando> {
await _fallbackPlayer.setVolume(volumen.clamp(0.0, 1.0));
}
/// Confirms the native-to-Flutter audio handoff at most once per screen
/// instance, then starts the single audible Dart fade-in ramp (Slice 3:
/// fade-in dedup at handoff). The native ramp
/// (`PluriWaveAlarmService.startFadeIn`) owns audio until this
/// confirmation lands; starting the Dart ramp any earlier would
/// interleave both ramps and produce an audible jump/reset. If the
/// native confirmation channel call fails (dead or never-there native
/// side), the fade-in still starts in the `finally` block below — Dart
/// is the only audible source either way, so the ring must never stay
/// stuck at [_volumenInicialFadeIn] forever.
Future<void> _confirmarAudioFlutterListo() async {
if (_audioFlutterConfirmado) return;
_audioFlutterConfirmado = true;
await context.read<EstadoAlarmas>().android.confirmarAudioFlutter(
widget.alarma.id,
);
try {
await context.read<EstadoAlarmas>().android.confirmarAudioFlutter(
widget.alarma.id,
);
} catch (e) {
debugPrint('[PluriWave][alarmas] confirmar audio flutter fallo: $e');
} finally {
_iniciarFadeIn();
}
}
/// Restores the ring-scoped `STREAM_MUSIC` override at most once per