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:
@@ -1,10 +1,12 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:pluriwave/l10n/gen/app_localizations.dart';
|
||||
import 'package:pluriwave/modelos/dispositivo_audio.dart';
|
||||
import 'package:pluriwave/modelos/emisora.dart';
|
||||
import 'package:pluriwave/modelos/grupo_favoritos.dart';
|
||||
import 'package:pluriwave/modelos/preset_ecualizador.dart';
|
||||
import 'package:pluriwave/servicios/servicio_audio.dart';
|
||||
import 'package:pluriwave/servicios/servicio_dispositivo_audio.dart';
|
||||
import 'package:pluriwave/servicios/servicio_ecualizador.dart';
|
||||
import 'package:pluriwave/servicios/servicio_favoritos.dart';
|
||||
import 'package:pluriwave/servicios/servicio_radio.dart';
|
||||
@@ -282,10 +284,16 @@ class FakeServicioEcualizador extends ServicioEcualizador {
|
||||
PresetEcualizador? principal,
|
||||
Map<String, PresetEcualizador>? porEmisora,
|
||||
bool activo = true,
|
||||
bool eqMultiDeviceEnabled = false,
|
||||
Map<String, PresetEcualizador>? presetsDispositivo,
|
||||
Map<String, PresetEcualizador>? presetsMatriz,
|
||||
}) : _config = ConfiguracionEcualizador(
|
||||
principal: principal ?? PresetEcualizador.flat,
|
||||
porEmisora: porEmisora ?? {},
|
||||
activo: activo,
|
||||
eqMultiDeviceEnabled: eqMultiDeviceEnabled,
|
||||
presetsDispositivo: presetsDispositivo ?? {},
|
||||
presetsMatriz: presetsMatriz ?? {},
|
||||
);
|
||||
|
||||
ConfiguracionEcualizador _config;
|
||||
@@ -300,6 +308,9 @@ class FakeServicioEcualizador extends ServicioEcualizador {
|
||||
principal: preset,
|
||||
porEmisora: _config.porEmisora,
|
||||
activo: _config.activo,
|
||||
eqMultiDeviceEnabled: _config.eqMultiDeviceEnabled,
|
||||
presetsDispositivo: _config.presetsDispositivo,
|
||||
presetsMatriz: _config.presetsMatriz,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -309,6 +320,9 @@ class FakeServicioEcualizador extends ServicioEcualizador {
|
||||
principal: _config.principal,
|
||||
porEmisora: _config.porEmisora,
|
||||
activo: activo,
|
||||
eqMultiDeviceEnabled: _config.eqMultiDeviceEnabled,
|
||||
presetsDispositivo: _config.presetsDispositivo,
|
||||
presetsMatriz: _config.presetsMatriz,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -320,6 +334,9 @@ class FakeServicioEcualizador extends ServicioEcualizador {
|
||||
principal: _config.principal,
|
||||
porEmisora: mapa,
|
||||
activo: _config.activo,
|
||||
eqMultiDeviceEnabled: _config.eqMultiDeviceEnabled,
|
||||
presetsDispositivo: _config.presetsDispositivo,
|
||||
presetsMatriz: _config.presetsMatriz,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -331,8 +348,95 @@ class FakeServicioEcualizador extends ServicioEcualizador {
|
||||
principal: _config.principal,
|
||||
porEmisora: mapa,
|
||||
activo: _config.activo,
|
||||
eqMultiDeviceEnabled: _config.eqMultiDeviceEnabled,
|
||||
presetsDispositivo: _config.presetsDispositivo,
|
||||
presetsMatriz: _config.presetsMatriz,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> guardarToggleMultiDispositivo(bool habilitado) async {
|
||||
_config = ConfiguracionEcualizador(
|
||||
principal: _config.principal,
|
||||
porEmisora: _config.porEmisora,
|
||||
activo: _config.activo,
|
||||
eqMultiDeviceEnabled: habilitado,
|
||||
presetsDispositivo: _config.presetsDispositivo,
|
||||
presetsMatriz: _config.presetsMatriz,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> guardarPresetDispositivo(
|
||||
String deviceId,
|
||||
PresetEcualizador preset,
|
||||
) async {
|
||||
final mapa = Map<String, PresetEcualizador>.from(
|
||||
_config.presetsDispositivo,
|
||||
);
|
||||
mapa[deviceId] = preset;
|
||||
_config = ConfiguracionEcualizador(
|
||||
principal: _config.principal,
|
||||
porEmisora: _config.porEmisora,
|
||||
activo: _config.activo,
|
||||
eqMultiDeviceEnabled: _config.eqMultiDeviceEnabled,
|
||||
presetsDispositivo: mapa,
|
||||
presetsMatriz: _config.presetsMatriz,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> guardarPresetMatriz(
|
||||
String clave,
|
||||
PresetEcualizador preset,
|
||||
) async {
|
||||
final mapa = Map<String, PresetEcualizador>.from(_config.presetsMatriz);
|
||||
mapa[clave] = preset;
|
||||
_config = ConfiguracionEcualizador(
|
||||
principal: _config.principal,
|
||||
porEmisora: _config.porEmisora,
|
||||
activo: _config.activo,
|
||||
eqMultiDeviceEnabled: _config.eqMultiDeviceEnabled,
|
||||
presetsDispositivo: _config.presetsDispositivo,
|
||||
presetsMatriz: mapa,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Fake implementation of [ServicioDispositivoAudio] for unit tests.
|
||||
///
|
||||
/// Use [emitirDispositivo] to push device change events without needing
|
||||
/// a real platform channel.
|
||||
class FakeServicioDispositivoAudio extends ServicioDispositivoAudio {
|
||||
final _controller = StreamController<DispositivoAudio>.broadcast();
|
||||
DispositivoAudio? _dispositivoActual;
|
||||
|
||||
@override
|
||||
DispositivoAudio? get dispositivoActual => _dispositivoActual;
|
||||
|
||||
@override
|
||||
Stream<DispositivoAudio> get onDispositivoCambiado => _controller.stream;
|
||||
|
||||
@override
|
||||
Future<DispositivoAudio> obtenerDispositivoActual() async {
|
||||
return _dispositivoActual ??
|
||||
const DispositivoAudio(
|
||||
id: 'builtin_speaker',
|
||||
tipo: TipoDispositivo.altavozInterno,
|
||||
nombre: 'Speaker',
|
||||
);
|
||||
}
|
||||
|
||||
/// Helper for tests: push a device change event.
|
||||
void emitirDispositivo(DispositivoAudio dispositivo) {
|
||||
_dispositivoActual = dispositivo;
|
||||
_controller.add(dispositivo);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
await _controller.close();
|
||||
}
|
||||
}
|
||||
|
||||
Emisora emisoraDemo({
|
||||
|
||||
Reference in New Issue
Block a user