Files
pluriwave/openspec/changes/eq-apply-lifecycle/explore.md
T
FreeTLab 0ab63731d0
Build & Deploy PluriWave / Análisis de código (push) Successful in 41s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 2m33s
fix(eq): re-apply equalizer when the native audio session rotates
ExoPlayer assigns a new audio session id after transient audio-focus
interruptions (navigation prompts, radar warnings), leaving the
AndroidEqualizer attached to the dead session so playback resumed
without equalization until the next station switch. The session-id
listener now detects genuine rotations through a dedicated guard and
re-activates the equalizer with the current preset, gated on EQ
availability to stay clear of player teardown/rebuild.
2026-07-10 18:51:45 +02:00

35 lines
3.9 KiB
Markdown

# Exploration: eq-apply-lifecycle (split into two changes)
Shared exploration for `eq-audiofocus-reapply` (Part B) and `eq-device-disconnect-revert` (Part A).
## Current State
The equalizer is `just_audio`'s `AndroidEqualizer` audio-pipeline effect (not a raw platform-channel `android.media.audiofx.Equalizer`), attached at `AudioPlayer` construction in `lib/servicios/servicio_audio.dart`. Device detection (`pluriwave/audio_devices`, `MainActivity.kt`) emits BOTH `onAudioDevicesAdded` and `onAudioDevicesRemoved` correctly — the native side is not the problem for Part A.
## Part B — EQ lost after audio-focus ducking (root cause CONFIRMED)
`AndroidEqualizer._activate(platform)` (just_audio 0.9.46, `just_audio.dart:3996`) re-attaches the effect to the native session, and it fires only from the platform-connect loop — which in this app only runs from `_cambiarFuente()` (explicit station switch, `servicio_audio.dart:449`). The native Android session id rotates independently on audio-focus transitions (fed from native `PlaybackEvent`s). Documented upstream ExoPlayer behavior (issue #5302): "a new audio session ID... attached equalizer and other audio effects stop working," workaround = re-initialize the equalizer every time the session id changes.
`servicio_audio.dart` already exposes `androidAudioSessionIdStream` (L197) but nothing listens to it for EQ purposes — only `lib/widgets/visualizador_audio.dart:82-88` consumes it, and it correctly re-syncs on every session-id change. **That is the reference pattern.** `servicio_audio_session.dart` (duck/pause/resume) never touches the EQ.
Fix shape: subscribe to `androidAudioSessionIdStream`, guard on changed+non-null (like the visualizer), call the existing idempotent choke-points `_activarEcualizador()` (L526) / `aplicarPreset()` (L540).
## Part A — device disconnect/cold-start revert (gap, not regression)
`_onDispositivoCambiado()` (`estado_ecualizador.dart:210-227`) is a single handler for every device event, connect or disconnect alike. There is zero "previous config" tracking anywhere. On disconnect it re-resolves the 4-level hierarchy for whatever device is now active (usually `builtin_speaker`), which coincidentally *looks like* revert-to-base only when no station/matrix override interferes. Zero disconnect test coverage in `test/estado/estado_ecualizador_test.dart`. The archived `eq-device-autoswitch-ux` change never touched disconnect.
### Revert semantics — DECISION (orchestrator)
Deterministic re-resolution of the existing 4-level hierarchy for the now-active device — NOT a literal snapshot/restore. Justification: the user's second scenario ("app opens with no device connected") has no snapshot to restore, so re-resolution is the only mechanism that covers both scenarios; it is also stateless and self-healing. What's missing is making the disconnect path explicit, correct, and tested (including cold-start resolution).
## Affected Areas
- `lib/estado/estado_ecualizador.dart``_onDispositivoCambiado()`, no disconnect/revert logic (Part A)
- `lib/servicios/servicio_audio.dart``_androidAudioSessionIdSub` (L258-265) stores the id but never re-applies EQ; `_activarEcualizador()`/`aplicarPreset()` are the reuse points (Part B)
- `lib/widgets/visualizador_audio.dart:82-88` — reference pattern for session-id reaction
- `test/estado/estado_ecualizador_test.dart` — no disconnect coverage
- `test/servicios/servicio_audio_session_test.dart`, `servicio_audio_source_switch_test.dart` — no EQ-reapply coverage
## Risks
- Part B: guard against re-firing on every session-id emission (including legitimate first activation); avoid racing `_recrearPlayer()` teardown/rebuild
- Part B must apply regardless of `_eqMultiDeviceEnabled` — ducking bug is orthogonal to the multi-device toggle
- Both: must not regress the passing connect/4-level-resolution suite or manual slider path (`cambiarBanda`/`setBanda`)