Files
pluriwave/test/servicios/servicio_audio_session_test.dart
T
FreeTLab 491585ad12 fix(eq): re-apply the equalizer after an audio-focus interruption
The equalizer stopped applying after another app interrupted audio (e.g. a
navigation app's voice prompt): play a station with EQ working, let the
prompt speak, resume -- the audio sounds flat until the station is
re-tapped.

debeReaplicarEcualizador only re-attaches the equalizer when the native
player session id actually changes. A short transient interruption keeps
the SAME session (no id rotation), so that trigger never fires, while
Android's AudioEffect framework can let a higher-priority client silently
disable this app's effect instance in the meantime.

Add reaplicarEcualizador() to ObjetivoAudioInterrumpible, implemented as a
thin delegate to the existing _activarEcualizador() (already the correct
idempotent setEnabled + re-push-gains path). ServicioAudioSession calls it
on resume-from-pause (after reanudar()) and on un-duck (after
setAtenuado(false)) -- additive to the existing session-id trigger, not a
replacement. The method takes no argument, so it can only re-assert
whatever enabled/disabled state the handler already holds -- an
interruption cycle with the equalizer OFF stays OFF.
2026-07-31 00:56:37 +02:00

140 lines
3.7 KiB
Dart

import 'package:audio_session/audio_session.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pluriwave/servicios/servicio_audio_session.dart';
class _ObjetivoFake implements ObjetivoAudioInterrumpible {
bool intencion = false;
bool reproduciendo = false;
int pausas = 0;
int reanudaciones = 0;
final List<bool> atenuaciones = [];
@override
bool get intencionReproducir => intencion;
@override
bool get estaReproduciendo => reproduciendo;
@override
Future<void> pausar() async {
pausas++;
reproduciendo = false;
intencion = false;
}
@override
Future<void> reanudar() async {
reanudaciones++;
reproduciendo = true;
intencion = true;
}
@override
Future<void> setAtenuado(bool atenuado) async {
atenuaciones.add(atenuado);
}
int reaplicaciones = 0;
@override
Future<void> reaplicarEcualizador() async {
reaplicaciones++;
}
}
/// S3-R1: audio-session interruptions (phone call, transient loss, duck) and
/// becoming-noisy (headphones unplugged) must pause/duck and auto-resume.
void main() {
test('interrupcion begin/pause pausa y baja la intencion (S3-R1)', () async {
final objetivo =
_ObjetivoFake()
..reproduciendo = true
..intencion = true;
final servicio = ServicioAudioSession(objetivo: objetivo);
await servicio.manejarInterrupcion(
AudioInterruptionEvent(true, AudioInterruptionType.pause),
);
expect(objetivo.pausas, 1);
expect(
objetivo.intencionReproducir,
isFalse,
reason: 'el reconnect de S7 no debe pelear con la llamada en curso',
);
});
test(
'interrupcion end/shouldResume reanuda la reproduccion (S3-R1)',
() async {
final objetivo =
_ObjetivoFake()
..reproduciendo = true
..intencion = true;
final servicio = ServicioAudioSession(objetivo: objetivo);
await servicio.manejarInterrupcion(
AudioInterruptionEvent(true, AudioInterruptionType.pause),
);
await servicio.manejarInterrupcion(
AudioInterruptionEvent(false, AudioInterruptionType.pause),
);
expect(objetivo.reanudaciones, 1);
expect(objetivo.intencionReproducir, isTrue);
},
);
test('end sin pausa previa por interrupcion NO reanuda', () async {
final objetivo = _ObjetivoFake();
final servicio = ServicioAudioSession(objetivo: objetivo);
await servicio.manejarInterrupcion(
AudioInterruptionEvent(false, AudioInterruptionType.pause),
);
expect(
objetivo.reanudaciones,
0,
reason:
'si el usuario ya estaba en pausa, el fin de llamada no arranca audio',
);
});
test('duck atenua al comenzar y restaura al terminar', () async {
final objetivo =
_ObjetivoFake()
..reproduciendo = true
..intencion = true;
final servicio = ServicioAudioSession(objetivo: objetivo);
await servicio.manejarInterrupcion(
AudioInterruptionEvent(true, AudioInterruptionType.duck),
);
await servicio.manejarInterrupcion(
AudioInterruptionEvent(false, AudioInterruptionType.duck),
);
expect(objetivo.atenuaciones, [true, false]);
expect(objetivo.pausas, 0);
});
test('becoming-noisy (auriculares desconectados) pausa (S3-R1)', () async {
final objetivo =
_ObjetivoFake()
..reproduciendo = true
..intencion = true;
final servicio = ServicioAudioSession(objetivo: objetivo);
await servicio.manejarDesconexionSalida();
expect(objetivo.pausas, 1);
// A later interruption end must NOT resume: unplugging is a hard pause.
await servicio.manejarInterrupcion(
AudioInterruptionEvent(false, AudioInterruptionType.pause),
);
expect(objetivo.reanudaciones, 0);
});
}