_onDispositivoCambiado() bootstrapped a device-level preset entry for every never-seen device id, including the built-in speaker. That persistent level-3 entry masked later global-preset edits (level 3 beats level 4 on every resolution), so disconnecting a BT device or cold-starting without one could leave the EQ stuck on an outdated copy instead of the current global preset. The base speaker is now excluded from the first-seen bootstrap: disconnect and cold start always resolve through the live hierarchy. BT/wired/USB devices keep their bootstrap behavior unchanged.
4.5 KiB
Proposal: EQ Device Disconnect / Cold-Start Revert
Intent
When an audio device disconnects — or the app opens with no external device connected — the EQ must return to the phone/base configuration, not stay on a stale device preset. Today _onDispositivoCambiado() (estado_ecualizador.dart:210-227) treats every device event identically: there is no explicit disconnect path, no cold-start guarantee, and ZERO disconnect test coverage. On disconnect it re-resolves the hierarchy for the now-active device, which only coincidentally looks like a revert when no station/matrix override interferes. This closes a correctness gap, not a regression.
Scope
In Scope
- Explicit disconnect handling: re-resolve + APPLY the preset for the now-active device (typically
builtin_speaker). - Cold-start guarantee: app open with no device connected applies the phone/base resolution, never a stale persisted device preset.
- Verify the resolved preset reaches the native EQ via the existing
audio.aplicarPreset()choke-point (not state-only mutation). - Test coverage: disconnect event, cold start without device, connect→disconnect→reconnect cycle, station/matrix override interaction.
- Respect
_eqMultiDeviceEnabled: when OFF, behavior is unchanged (2-level resolution).
Out of Scope
- Audio-focus ducking EQ re-apply (separate change
eq-audiofocus-reapply, Part B). - Native Kotlin/Swift device detection (
MainActivity.kt,pluriwave/audio_devices) — already emits add/remove correctly. - New persistence fields or migration; matrix/station/device preset storage is unchanged.
Capabilities
New Capabilities
- None.
Modified Capabilities
multi-device-eq: revert-on-disconnect and cold-start resolution become explicit, guaranteed requirements. "Revert" = deterministic re-resolution of the existing 4-level hierarchy (matrix → station → device → global) for the now-active device — NOT a snapshot/restore. This is stateless, self-healing, and the only mechanism covering the cold-start case (no snapshot exists to restore).
Approach
Make the disconnect path explicit inside the device-event handler (_onDispositivoCambiado, or a split connect/disconnect dispatch). On any device event — connect, disconnect, or cold-start seed — set _dispositivoActualId to the now-active device, run _resolverPresetActivo(), assign _presetActual, and push it through audio.aplicarPreset(). Cold start already flows through _sembrarDispositivoActual() → obtenerDispositivoActual() (fallback builtin_speaker); the change guarantees the resolved base preset is APPLIED on that path, not just seeded. The first-seen bootstrap (presetsDispositivo write) must NOT hijack revert resolution for the base device.
Affected Areas
| Area | Impact | Description |
|---|---|---|
lib/estado/estado_ecualizador.dart |
Modified | Explicit disconnect/cold-start resolve+apply in the device handler |
test/estado/estado_ecualizador_test.dart |
Modified | New disconnect, cold-start, reconnect-cycle, override-matrix cases |
Risks
| Risk | Likelihood | Mitigation |
|---|---|---|
| Regress passing connect/4-level suite (Phase 5, tests 5.1–5.8, 2.x) | Med | Reuse _resolverPresetActivo(); run full estado_ecualizador_test.dart green before/after |
| First-seen bootstrap overwrites base on disconnect | Med | Guard bootstrap so revert to builtin_speaker resolves from hierarchy, not a forced copy |
| Double-apply / redundant native pushes on rapid events | Low | Idempotent apply choke-point; skip when resolved preset unchanged |
Rollback Plan
Single-file logic change plus tests. Revert the commit(s) touching estado_ecualizador.dart and its test file; no persistence schema, native, or public-API change means rollback restores the prior coincidental-revert behavior with zero migration.
Dependencies
- Shared exploration
sdd/eq-apply-lifecycle/explore(Part A section). Locked decision: re-resolution, not snapshot/restore.
Success Criteria
- Disconnect event re-resolves and applies the base-device preset via
aplicarPreset(). - Cold start with no device applies phone/base resolution, not a stale device preset.
- connect→disconnect→reconnect cycle lands on the correct preset at each step, including station/matrix overrides.
- With
_eqMultiDeviceEnabled=false, behavior is byte-for-byte unchanged (2-level). - Full
estado_ecualizador_test.dartsuite stays green (no connect-path regression).