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.
5.0 KiB
EQ Audio-Focus Re-Apply Specification
Purpose
Keep the native Android equalizer attached and equalized after ExoPlayer
rotates the audio session id mid-playback (audio-focus transitions, ducking by
other apps). Today re-attachment only happens on explicit station switch, so
music resumed after an interruption is silently un-equalized. Covers
PluriWaveAudioHandler (lib/servicios/servicio_audio.dart) only.
Requirements
Requirement: Session Id Rotation Triggers EQ Re-Apply
The system MUST re-attach the equalizer and re-push the current preset's gains
whenever androidAudioSessionIdStream emits a session id that differs from the
last one processed for EQ purposes, is non-null, and _eqDisponible == true.
Change tracking MUST use a dedicated field (_ultimaSessionIdEq), separate
from the broadcast field _androidAudioSessionId, so existing broadcast
semantics on androidAudioSessionIdStream stay unchanged.
Scenario: Session id rotates while playing
- GIVEN the player is playing, EQ attached/enabled (
_eqDisponible == true), a non-flat preset applied - WHEN
androidAudioSessionIdStreamemits a new non-null id different from_ultimaSessionIdEq - THEN the system re-attaches the EQ (re-probes
_eq.parameters, re-enables per_ecualizadorActivo) and re-applies the current preset's gains - AND
_ultimaSessionIdEqupdates to the new id
Scenario: Same id re-emitted produces no redundant re-apply
- GIVEN
_ultimaSessionIdEqalready equals the last processed id - WHEN the same id is emitted again
- THEN the system MUST NOT call the re-attach/re-apply choke-point again
Scenario: First legitimate activation is not double-applied
- GIVEN a station switch just completed
_recrearPlayer()and its own_activarEcualizador()call (servicio_audio.dart:449) - WHEN the resulting first session-id emission for the new player reaches this listener
- THEN the listener MAY invoke the same idempotent choke-point, but this MUST NOT produce a different final gain state than the station-switch path alone
Requirement: Re-Apply Is Gated On EQ Availability
The system MUST NOT attempt to re-attach or re-apply gains while
_eqDisponible == false, to avoid racing _recrearPlayer()'s teardown/rebuild.
Scenario: Rotation during player teardown is safely skipped
- GIVEN
_recrearPlayer()has set_eqDisponible = false, nulled_androidAudioSessionId, and created a freshAndroidEqualizer() - WHEN a session-id emission reaches the listener while
_eqDisponibleis stillfalse - THEN the system skips the re-apply choke-point entirely
- AND no exception is thrown
Requirement: Re-Apply Is Independent Of Multi-Device Toggle
The system MUST re-apply gains on session id rotation regardless of
_eqMultiDeviceEnabled, since this fix lives below the EstadoEcualizador
multi-device layer.
Scenario: Re-apply works with the toggle on or off
- GIVEN either state of
_eqMultiDeviceEnabled - WHEN a session id rotates under the "rotates while playing" preconditions
- THEN the re-apply behaves identically in both states
Non-Goals
- Station-switch EQ path (
_cambiarFuente→_recrearPlayer→_activarEcualizador()at line 449) is unchanged. - Manual slider path (
cambiarBanda/setBanda) is unchanged. - No new localized strings — no l10n keys added, removed, or modified.
Testability Matrix
| Scenario | Testable how |
|---|---|
| Rotates while playing / same-id guard / first-activation no double-apply / teardown-race skip / toggle-independence | Manual QA only. PluriWaveAudioHandler is never instantiated anywhere in test/ — confirmed zero matches for PluriWaveAudioHandler(. Its _player is a real just_audio.AudioPlayer requiring platform MethodChannels; androidAudioSessionIdStream is fed directly from it, with no fake/injectable seam. Verify on-device: real ducking (e.g. Google Maps prompt) during playback, and rapid station-switch during a ducking event for the teardown race. |
| Station-switch path / manual slider path unchanged | Regression only, not targeted unit tests. Same instantiation blocker applies to both call sites; confidence comes from the full flutter test suite plus manual QA, not new automated tests against these paths. |
Correction to proposal: the proposal's downstream notes claim
servicio_audio_source_switch_test.dart shows "fake/stream patterns" for this
handler. Verified against the file: it explicitly states "We cannot
instantiate the handler in unit tests (MethodChannels)" and only exercises
ControladorReconexion in isolation — not PluriWaveAudioHandler. No
fake/injectable AudioPlayer/AndroidEqualizer seam exists in test/. Every
scenario above is therefore manual on-device QA under the current harness, not
automated. This reframes the proposal's Success Criteria item "covered by
passing tests" as "passing tests where the harness allows, manual QA
otherwise" — sdd-design/sdd-tasks should decide whether to accept that, or
add a DI seam for _player/_eq as a separate prerequisite (out of this
change's scope).