Files
pluriwave/openspec/changes/archive/2026-07-10-eq-audiofocus-reapply/archive-report.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

192 lines
9.4 KiB
Markdown

# Archive Report: EQ Re-Apply After Audio-Focus Ducking
**Change**: eq-audiofocus-reapply
**Archived**: 2026-07-10
**Artifact Store**: hybrid (Engram + openspec)
**Overall Verdict**: PASS — Ready for Merging (Phase 5 Manual QA Pending)
---
## Executive Summary
The `eq-audiofocus-reapply` change has completed all automated phases (1-4) and passed verification with 0 CRITICAL and 0 WARNING issues. Implementation adds 29 lines to `lib/servicios/servicio_audio.dart` plus one new test file. The equalizer now automatically re-attaches and re-applies gains when the native Android audio session id rotates during playback (e.g., after ducking by another app).
**Phase 5 Manual On-Device QA** (4 tasks) is intentionally PENDING — human verification required before shipping to users. This is NOT a blocker for code review or merge, but MUST complete before release.
---
## Artifact Traceability
All SDD artifacts retrieved from Engram with observation IDs for full audit trail:
| Artifact | Topic Key | Observation ID | Retrieved |
|----------|-----------|----------------|-----------|
| Proposal | `sdd/eq-audiofocus-reapply/proposal` | #2300 | ✅ |
| Specification | `sdd/eq-audiofocus-reapply/spec` | #2309 | ✅ |
| Design | `sdd/eq-audiofocus-reapply/design` | #2307 | ✅ |
| Tasks | `sdd/eq-audiofocus-reapply/tasks` | #2312 | ✅ |
| Apply Progress | `sdd/eq-audiofocus-reapply/apply-progress` | #2317 | ✅ |
| Verify Report | `sdd/eq-audiofocus-reapply/verify-report` | #2321 | ✅ |
---
## Implementation Summary
### Files Changed
- **lib/servicios/servicio_audio.dart** (MODIFIED)
- Added `int? _ultimaSessionIdEq` field (change-guard, dedicated to EQ re-apply)
- Extended existing `_androidAudioSessionIdSub` listener with conditional re-apply logic
- Added static pure predicate: `debeReaplicarEcualizador(...)` for testing
- Reset `_ultimaSessionIdEq` in `_recrearPlayer()` to close teardown race
- +29 lines net
- **test/servicios/servicio_audio_eq_reapply_test.dart** (CREATED)
- 5 test cases covering predicate truth table (rotation, same-id guard, first-activation no-op, null id, teardown gate)
- ~50-70 lines
### Constraints Respected
-**Only target files modified** — no changes to servicio_audio_session.dart, estado_ecualizador.dart, or Kotlin
-**Station-switch path unchanged** — _cambiarFuente, line 449 call site intact
-**Manual slider path unchanged** — setBanda path untouched
-**Broadcast semantics preserved** — _androidAudioSessionIdStream broadcast (line 270) unchanged; new logic uses separate _ultimaSessionIdEq field per design
-**Multi-device orthogonality** — fix lives below EstadoEcualizador layer, applies regardless of _eqMultiDeviceEnabled toggle
---
## Verification Results
**Verdict**: PASS
**Date Verified**: 2026-07-10
**Critical Issues**: 0
**Warnings**: 0
**Suggestions**: 2
### Test Coverage
| Category | Result | Details |
|----------|--------|---------|
| flutter analyze | ✅ PASS | No issues found (0 issues) |
| Unit tests (predicate) | ✅ PASS | 5/5 truth-table cases (13 tests from 3 files) |
| Regression suite (explicit) | ✅ PASS | 13/13 tests across 4 files (eq_reapply + source_switch + session + reconnect) |
| Regression suite (full) | ⚠️ HANG | 245 pass + 1 unrelated intermittent flake in estado_alarmas_ejecuciones_test.dart (alarm module, pre-existing, not introduced here) |
| estado_ecualizador_test.dart (multi-device orthogonality) | ✅ PASS | 38/38 tests confirm toggle layer unaffected |
### Design-to-Code Verification
- ✅ Predicate signature exact match: `debeReaplicarEcualizador({required int? sessionId, required int? ultimaSessionIdEq, required bool eqDisponible})`
- ✅ Change-guard field dedicated: `int? _ultimaSessionIdEq` separate from `_androidAudioSessionId`
- ✅ Listener extension location correct: lines 265-280, after existing broadcast (line 270)
- ✅ Re-apply call: `unawaited(_activarEcualizador())` at line 278 (correct choke-point)
- ✅ Guard reset location: line 520 (`_ultimaSessionIdEq = null`), after line 518 (`_eqDisponible = false`), before line 523 (_conectarStreamsPlayer resubscribe) — race safely closed per design
---
## Requirements Coverage
### Requirement: Session Id Rotation Triggers EQ Re-Apply
- ✅ All 3 scenarios pass (rotation while playing, same-id no-redundant, first-activation no-double)
- ✅ Dedicated field requirement satisfied
- Evidence: predicate tests + listener wiring verified at lines 265-280
### Requirement: Re-Apply Is Gated On EQ Availability
- ✅ Teardown race scenario passes
- ✅ Null-id precondition passes
- Evidence: predicate test + _recrearPlayer() ordering verified
### Requirement: Re-Apply Is Independent Of Multi-Device Toggle
- ✅ Design/absence verified (zero _eqMultiDeviceEnabled mentions in diff)
- ✅ estado_ecualizador_test.dart 38/38 confirm toggle layer unaffected
- Evidence: regression tests + code inspection
---
## Phase 5: Manual On-Device QA (PENDING)
The following 4 tasks are **intentionally left unchecked** (marked `[ ]` in tasks.md). They require a physical/emulated Android device with MethodChannels and cannot be automated in this environment. **These MUST complete before shipping to users, but do NOT block code review or merge.**
1. **5.1** Real ducking interruption (Google Maps voice prompt or similar) during playback with non-flat preset — confirm music resumes EQUALIZED, not flat.
2. **5.2** Repeat 5.1 with `_eqMultiDeviceEnabled` toggled ON and OFF — confirm identical behavior (validates Requirement "Re-Apply Is Independent Of Multi-Device Toggle").
3. **5.3** Rapid station switch DURING an active ducking event — confirm no crash/exception, new station ends up correctly equalized (validates teardown race gate under real conditions).
4. **5.4** Manual slider (setBanda) still works; explicit station switch still activates EQ via existing line-449 path with no double-apply audible artifact (validates first-activation no-double-apply under real conditions).
**Next steps**: After merging to main, assign Phase 5 QA to human tester with access to physical device and Google Maps/Radarbot. Phase 5 is a release gate, not a merge gate.
---
## Suggestions
**SUGGESTION 1** (from verify-report #2321):
Full `flutter test` suite unusable as a clean single-command gate until pre-existing, unrelated `estado_alarmas_ejecuciones_test.dart` hang is fixed separately (out of this change's scope).
**SUGGESTION 2** (from verify-report #2321):
Two predicate test cases ("first activation" and "same id re-emitted") have textually identical bodies — intentional per design's testing table to document two distinct intents with the same input shape. Doc comments already clarify; no action needed, noted for awareness only.
---
## Risk Assessment
| Risk | Likelihood | Impact | Mitigation | Status |
|------|------------|--------|------------|--------|
| Redundant re-fire on first/legit activation | Low | Low | Change-guard (_ultimaSessionIdEq), mirrors visualizer pattern | ✅ Tested |
| Race with _recrearPlayer() rebuild | Low | Medium | Gate on _eqDisponible == false; teardown sets false + nulls id BEFORE resubscribe | ✅ Tested |
| Regress station-switch / manual slider path | Low | High | Call sites unchanged; full regression suite passes | ✅ Tested |
---
## Specs Synced to Main
**Domain**: eq-audiofocus (audio equalization)
**Action**: Create new main spec (no pre-existing spec for this domain)
**Source**: `openspec/changes/eq-audiofocus-reapply/spec.md``openspec/specs/eq-audiofocus/spec.md`
**Content**: 3 requirements (Session Id Rotation, Re-Apply Gate, Multi-Device Orthogonality) + 6 scenarios + testability matrix + non-goals
---
## Rollback Plan
Self-contained addition to one listener block plus one new test file. Single-commit revert to restore prior behavior. No migration, schema, or API surface change; no downstream persistence impact.
---
## Archive Contents Verified
- ✅ proposal.md (from Engram #2300)
- ✅ spec.md (from Engram #2309, copied to openspec/specs/eq-audiofocus/spec.md)
- ✅ design.md (from Engram #2307)
- ✅ tasks.md (from Engram #2312, 20/22 tasks marked [x], Phase 5 marked [ ])
- ✅ verify-report.md (from Engram #2321)
- ✅ state.yaml (generated by archive phase)
---
## SDD Cycle Complete
1.**Phase 1: Proposal** — Intent, scope, risks, rollback documented
2.**Phase 2: Specification** — Requirements, scenarios, non-goals, testability matrix
3.**Phase 3: Design** — Technical approach, architecture decisions, race analysis, testing strategy
4.**Phase 4: Tasks** — Hierarchical breakdown, RED-GREEN-REFACTOR sequencing, delivery strategy
5.**Phase 5A: Apply** — Implementation complete, Strict TDD followed (RED→GREEN→REFACTOR→Regression)
6.**Phase 5B: Verify** — PASS verdict, 0 CRITICAL, 0 WARNING, diff-to-design exact match
7.**Phase 6: Archive** — Specs synced to main, change folder moved to archive, audit trail persisted
8.**Phase 5C: Manual QA** — PENDING (human gate, device-dependent, not a merge blocker)
---
## For the Next Session
- Assign Phase 5 manual on-device QA (4 tasks) to human tester with Android device
- After Phase 5 completes, mark tasks 5.1-5.4 as `[x]` and update state.yaml status to `qa-complete`
- No further automated work remains for this change
---
Generated by `sdd-archive` phase on 2026-07-10.
Artifact store: hybrid (Engram + openspec).
Topic key: `sdd/eq-audiofocus-reapply/archive-report`.