feat(eq): add per-device equalizer with 4-level preset resolution
Introduce multi-device EQ support allowing each audio output device (built-in speaker, wired, USB, individual Bluetooth by MAC) to have its own equalizer preset, combined with existing per-station presets for a full station×device matrix. - Add DispositivoAudio model and ServicioDispositivoAudio interface - Add Android platform channel (AudioDeviceCallback) for device detection - Add iOS AudioDevicesPlugin (AVAudioSession route tracking) - Extend ServicioEcualizador with device and matrix persistence keys - Implement 4-level resolution: matrix > station > device > global - Add advanced EQ settings section with feature toggle (off by default) - Extend export/import to v3 with backward compatibility - 184 tests passing, zero analyzer issues
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:pluriwave/modelos/dispositivo_audio.dart';
|
||||
import 'package:pluriwave/servicios/servicio_dispositivo_audio.dart';
|
||||
|
||||
import '../helpers/fakes.dart';
|
||||
|
||||
/// Null (toggle-off) implementation that returns empty data without errors.
|
||||
/// Represents the behavior when `eqMultiDeviceEnabled` is false.
|
||||
class NullServicioDispositivoAudio extends ServicioDispositivoAudio {
|
||||
@override
|
||||
DispositivoAudio? get dispositivoActual => null;
|
||||
|
||||
@override
|
||||
Stream<DispositivoAudio> get onDispositivoCambiado => const Stream.empty();
|
||||
|
||||
@override
|
||||
Future<DispositivoAudio> obtenerDispositivoActual() async {
|
||||
return const DispositivoAudio(
|
||||
id: 'builtin_speaker',
|
||||
tipo: TipoDispositivo.altavozInterno,
|
||||
nombre: 'Speaker',
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {}
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('Toggle-off path: NullServicioDispositivoAudio', () {
|
||||
late NullServicioDispositivoAudio servicio;
|
||||
|
||||
setUp(() {
|
||||
servicio = NullServicioDispositivoAudio();
|
||||
});
|
||||
|
||||
test('dispositivoActual is always null', () {
|
||||
expect(servicio.dispositivoActual, isNull);
|
||||
});
|
||||
|
||||
test('onDispositivoCambiado emits no events', () async {
|
||||
final received = <DispositivoAudio>[];
|
||||
final sub = servicio.onDispositivoCambiado.listen(received.add);
|
||||
await Future<void>.delayed(const Duration(milliseconds: 10));
|
||||
await sub.cancel();
|
||||
expect(received, isEmpty);
|
||||
});
|
||||
|
||||
test('obtenerDispositivoActual returns fallback builtin_speaker', () async {
|
||||
final device = await servicio.obtenerDispositivoActual();
|
||||
expect(device.id, 'builtin_speaker');
|
||||
expect(device.tipo, TipoDispositivo.altavozInterno);
|
||||
});
|
||||
|
||||
test('dispose completes without error', () async {
|
||||
await expectLater(servicio.dispose(), completes);
|
||||
});
|
||||
|
||||
test('no platform channel code is invoked (fake, not real)', () async {
|
||||
// Verify that FakeServicioDispositivoAudio also produces no events
|
||||
// when no emitirDispositivo is called — simulating toggle-off behavior.
|
||||
final fake = FakeServicioDispositivoAudio();
|
||||
final received = <DispositivoAudio>[];
|
||||
final sub = fake.onDispositivoCambiado.listen(received.add);
|
||||
await Future<void>.delayed(const Duration(milliseconds: 10));
|
||||
await sub.cancel();
|
||||
expect(received, isEmpty);
|
||||
await fake.dispose();
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user