_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.
63 lines
4.5 KiB
Markdown
63 lines
4.5 KiB
Markdown
# 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.dart` suite stays green (no connect-path regression).
|