From 5291221fc84a1c06c7a62a94d2d4cfd927442fb6 Mon Sep 17 00:00:00 2001 From: freetlab Date: Sat, 11 Jul 2026 22:48:55 +0200 Subject: [PATCH] fix(alarm): cap the ring at the configured volume instead of the device max The ring-scoped media override forced STREAM_MUSIC to the hardware maximum, so the alarm's configured percentage was applied on top of a maxed speaker: "50%" meant 50% of the phone's absolute maximum and the fade rode against that ceiling, far louder than the device- relative level users were used to. On-device logcat also showed the just_audio player emitting one buffer at volume 1.0 before the 5% pre-start took effect, a full-scale click on the maxed stream. The stream is now capped at the alarm's configured volume (still independent of the device's own level, so it rings at device-volume 0), and the player ramps from ~5% up to its full range under that cap. Perceived peak is the configured fraction of the device maximum, reached gradually; the opening click drops to the configured fraction instead of full scale. --- lib/app.dart | 9 ++++++++- lib/pantallas/pantalla_alarma_sonando.dart | 8 +++++++- test/pantallas/pantalla_alarma_sonando_test.dart | 8 +++++--- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/lib/app.dart b/lib/app.dart index befc569..11edbbd 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -378,8 +378,15 @@ class _PaginaPrincipalState extends State<_PaginaPrincipal> { // effect for the whole ring, including fallback-WAV-only alarms that // never reach the station-playback branch below (Requirement: // Ring-scoped device-volume override). + // + // The media stream is capped at the alarm's CONFIGURED volume, not the + // device max: this makes the ring independent of the device's own volume + // (audible even at 0) while keeping "50%" meaning 50% of the phone's + // maximum. The player then ramps from ~5% up to full under this cap, so + // the perceived peak is exactly the configured fraction of max, reached + // gradually — not the device-relative level, and not a full-blast max. await context.read().android.forzarVolumenMediaParaAlarma( - 1.0, + alarma.volumen.clamp(0.0, 1.0), ); if (!mounted) return; final emisora = alarma.emisora; diff --git a/lib/pantallas/pantalla_alarma_sonando.dart b/lib/pantallas/pantalla_alarma_sonando.dart index 2411803..1ae8f8e 100644 --- a/lib/pantallas/pantalla_alarma_sonando.dart +++ b/lib/pantallas/pantalla_alarma_sonando.dart @@ -111,7 +111,13 @@ class _PantallaAlarmaSonandoState extends State { void _iniciarFadeIn() { _fadeInTimer?.cancel(); - final volumenObjetivo = widget.alarma.volumen.clamp(0.0, 1.0); + // The media stream is already capped at the alarm's configured volume + // (forzarVolumenMediaParaAlarma), so the player ramps up to its OWN full + // range under that cap. Perceived peak = configured% of the device max, + // reached gradually from ~5% of the cap; ramping the player to + // widget.alarma.volumen here would double-attenuate (configured x + // configured) and undershoot the level the user chose. + const volumenObjetivo = 1.0; final inicio = _volumenInicialFadeIn.clamp(0.0, volumenObjetivo); final segundosFade = widget.alarma.fadeInSegundos.clamp(0, 60); if (segundosFade <= 0 || volumenObjetivo <= inicio) { diff --git a/test/pantallas/pantalla_alarma_sonando_test.dart b/test/pantallas/pantalla_alarma_sonando_test.dart index 6945fb5..fc510fe 100644 --- a/test/pantallas/pantalla_alarma_sonando_test.dart +++ b/test/pantallas/pantalla_alarma_sonando_test.dart @@ -205,7 +205,9 @@ void main() { await tester.pumpAndSettle(); expect(entorno.android.detenidas, contains('ring1')); - expect(entorno.audio.volumenesAplicados, [0.05, 0.85]); + // Player ramps to its OWN full range (1.0); the configured level is + // enforced by the media-stream cap, not by the player target. + expect(entorno.audio.volumenesAplicados, [0.05, 1.0]); }); testWidgets('si confirmar el audio con el nativo falla, el fade-in de Dart ' @@ -225,7 +227,7 @@ void main() { // unica fuente audible, y el ring no debe quedar pegado en // _volumenInicialFadeIn para siempre. expect(entorno.android.detenidas, isEmpty); - expect(entorno.audio.volumenesAplicados, [0.05, 0.85]); + expect(entorno.audio.volumenesAplicados, [0.05, 1.0]); }); }); @@ -247,7 +249,7 @@ void main() { ); expect( entorno.audio.volumenesAplicados, - [0.05, 0.85], + [0.05, 1.0], reason: 'el fade-in debe arrancar tambien en el camino ya-sonando', ); });