Files
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

3.9 KiB

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 PlaybackEvents). 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)