Persist the exploration, proposal, spec, design, tasks, and verify/archive reports produced during the multi-device EQ, alarm-countdown, and notification-visual-polish SDD cycles.
13 KiB
Spec: Multi-Device Equalizer
New Capability: audio-device-detection
Purpose
Platform channel bridge that enumerates current audio output devices and streams change events to Dart. Enables the rest of the system to react to device connect/disconnect without polling.
Requirements
Requirement: Device enumeration on demand
The system MUST expose a synchronous query that returns the list of currently connected audio output devices as a typed list of device IDs.
Scenario: enumerate at startup — Android
- GIVEN the feature toggle is enabled
- WHEN
ServicioDispositivoAudio.dispositivosActuales()is called on Android - THEN it MUST return a list where each entry has a stable
deviceIdstring - AND built-in speaker MUST appear as
"builtin_speaker" - AND BT A2DP devices MUST appear as
"bt_a2dp:<MAC>" - AND USB audio MUST appear as
"usb_headset:<address>" - AND wired headset MUST appear as
"wired_headset"
Scenario: enumerate at startup — iOS
- GIVEN the feature toggle is enabled
- WHEN
ServicioDispositivoAudio.dispositivosActuales()is called on iOS - THEN it MUST return at least the active route output
- AND the
deviceIdMUST be stable within the session usingportType+uid - AND calling it again before a route change MUST return the same IDs
Scenario: enumerate when toggle is disabled
- GIVEN the feature toggle is disabled
- WHEN any method of
ServicioDispositivoAudiois called - THEN it MUST return an empty list and an empty stream without errors
Requirement: Device change stream
The system MUST provide a Stream<DispositivoAudio> that emits an event whenever an audio output device is connected or disconnected.
Scenario: Bluetooth device connects — Android
- GIVEN the feature toggle is enabled and stream is subscribed
- WHEN a Bluetooth A2DP device connects
- THEN the stream MUST emit a
DispositivoAudiowith the correct MAC-baseddeviceId - AND the event MUST arrive within 1 second of the OS callback
Scenario: wired headset disconnects
- GIVEN a wired headset is connected and the stream is subscribed
- WHEN the headset is unplugged
- THEN the stream MUST emit a disconnect event for
"wired_headset"
Scenario: stream delivers no events when toggle is off
- GIVEN the feature toggle is disabled
- WHEN a device is connected or disconnected
- THEN the stream MUST NOT emit any events
Requirement: Stable device key derivation
The system MUST derive a deterministic, collision-free device key from platform device metadata such that the same physical device always yields the same key across app restarts.
Scenario: BT MAC key is stable across restarts — Android
- GIVEN a Bluetooth A2DP device with MAC
AA:BB:CC:DD:EE:FF - WHEN the app restarts and the device reconnects
- THEN
deviceIdMUST be"bt_a2dp:AA:BB:CC:DD:EE:FF"on both launches
Scenario: iOS uid fallback on uid instability
- GIVEN a Bluetooth device whose
uiddiffers across sessions on iOS - WHEN the key is derived
- THEN the system MUST use
portType+portNameas the fallback key - AND MUST NOT produce an empty or null key
Requirement: Fakeable service interface
ServicioDispositivoAudio MUST be abstracted behind an interface so a FakeServicioDispositivoAudio can inject controlled device streams in unit tests without platform channels.
Scenario: unit test uses fake
- GIVEN a test instantiates
EstadoEcualizadorwithFakeServicioDispositivoAudio - WHEN the fake emits a device ID via its controller
- THEN
EstadoEcualizadorMUST react as if a real device change occurred - AND no platform channel code MUST be invoked
New Capability: multi-device-eq
Purpose
Device-aware EQ resolution, persistence of per-device and matrix presets, and automatic preset swap when the active output device changes. Gated by a feature toggle that defaults to off.
Requirements
Requirement: Feature toggle defaults off
The system MUST gate all multi-device-eq behavior behind a persistent boolean flag eq_multi_device_enabled_v1 that defaults to false.
Scenario: toggle defaults to off on first install
- GIVEN the app is installed fresh with no prior SharedPreferences data
- WHEN
EstadoEcualizadorinitializes - THEN
eqMultiDeviceEnabledMUST befalse - AND EQ behavior MUST be identical to the pre-feature release
Scenario: enabling the toggle persists across restarts
- GIVEN the user enables multi-device EQ in Settings
- WHEN the app is killed and relaunched
- THEN
eqMultiDeviceEnabledMUST still betrue
Scenario: disabling the toggle restores legacy behavior immediately
- GIVEN the toggle is currently
true - WHEN the user sets it to
false - THEN all EQ resolution MUST immediately fall back to the 2-level hierarchy (station → global)
- AND no per-device or matrix presets MUST be applied
Requirement: 4-level EQ resolution hierarchy
When the feature toggle is enabled, the system MUST resolve the active EQ preset using the following priority order, stopping at the first non-null match:
presetsMatriz["stationUuid:deviceId"]— station × devicepresetsEmisoraMap[stationUuid]— station-onlypresetsDispositivo[deviceId]— device-onlypresetPrincipal— global fallback
Scenario: station+device preset takes top priority
- GIVEN a matrix entry exists for
stationUuid:deviceId - WHEN EQ is resolved for that station playing on that device
- THEN the matrix preset MUST be returned
Scenario: falls back to station when no matrix entry
- GIVEN no matrix entry for
stationUuid:deviceId - AND a station-only preset exists for
stationUuid - WHEN EQ is resolved
- THEN the station-only preset MUST be returned
Scenario: falls back to device when no station preset
- GIVEN no matrix entry and no station preset
- AND a device-only preset exists for
deviceId - WHEN EQ is resolved
- THEN the device preset MUST be returned
Scenario: falls back to global when nothing else matches
- GIVEN no matrix, station, or device preset
- WHEN EQ is resolved
- THEN
presetPrincipalMUST be returned
Scenario: toggle off collapses to 2-level hierarchy
- GIVEN
eqMultiDeviceEnabledisfalse - WHEN EQ is resolved for any station/device combination
- THEN the system MUST use only station → global resolution (original behavior)
Requirement: First-device initialization copies current preset
When a device is seen for the first time (no entry in presetsDispositivo), the system MUST copy the currently active resolved preset as the device's default starting point.
Scenario: new device gets a copy of current preset
- GIVEN a device with ID
"bt_a2dp:AA:BB:CC:DD:EE:FF"has never been seen - WHEN that device connects
- THEN
presetsDispositivo["bt_a2dp:AA:BB:CC:DD:EE:FF"]MUST be initialized to the current resolved preset - AND no prompt or confirmation MUST be required from the user
Scenario: subsequent connections do not overwrite user-saved preset
- GIVEN the user has previously customized the device preset
- WHEN the same device reconnects
- THEN the user's preset MUST be preserved unchanged
Requirement: Automatic EQ swap on device change
When a device change event arrives and the feature toggle is on, the system MUST re-resolve and re-apply the EQ preset within 500 ms of the event.
Scenario: EQ swaps when BT device connects
- GIVEN the feature toggle is enabled and a station is playing via built-in speaker
- WHEN a BT A2DP device connects
- THEN the EQ preset resolved for
currentStation × "bt_a2dp:<MAC>"MUST be applied - AND the swap MUST complete within 500 ms
Scenario: EQ reverts when BT device disconnects
- GIVEN a station is playing via BT device with a matrix preset
- WHEN the BT device disconnects
- THEN EQ resolution MUST fall back to built-in speaker (or next best level)
- AND the player MUST receive the updated preset without user action
Scenario: no playback active during device change
- GIVEN no station is currently playing
- WHEN a device change event arrives
- THEN the system MUST update internal device state silently
- AND the next playback MUST use the resolved preset for the new device
Requirement: Per-device and matrix preset persistence
The system MUST persist per-device presets under eq_preset_por_dispositivo_v1 and matrix presets under eq_presets_matriz_v1 in SharedPreferences.
Scenario: device preset survives app restart
- GIVEN the user has saved a custom preset for device
"bt_a2dp:AA:BB:CC:DD:EE:FF" - WHEN the app restarts and the device reconnects
- THEN the same preset MUST be loaded from SharedPreferences
Scenario: matrix preset survives app restart
- GIVEN a
"stationUuid:deviceId"entry exists in the matrix - WHEN the app restarts
- THEN
presetsMatrizMUST contain that entry after initialization
Scenario: storage budget stays within limits
- GIVEN up to 50 stations × 5 device types (250 matrix entries) are stored
- WHEN all entries are populated
- THEN total SharedPreferences storage for device/matrix presets MUST remain under 50 KB
Requirement: Settings UI for feature toggle and device preset list
The Settings screen MUST expose an "Advanced Equalization Options" section that is visible only when advanced EQ is relevant to the user.
Scenario: toggle appears in Settings
- GIVEN the user navigates to Settings
- WHEN they scroll to the EQ section
- THEN an "Advanced Equalization Options" group MUST be visible
- AND it MUST contain a toggle labeled to enable per-device EQ
Scenario: device list appears when toggle is on
- GIVEN the toggle is enabled and at least one audio device has been detected
- WHEN the user views the Advanced EQ section
- THEN a list of known devices with their associated preset names MUST be visible
Scenario: Settings section is absent when toggle is off
- GIVEN the toggle is
false - WHEN the user views Settings
- THEN device preset list and matrix controls MUST NOT be displayed
Delta for equalizer
MODIFIED Requirements
Requirement: EQ resolution logic
The system MUST resolve the active EQ preset using the 4-level hierarchy (station×device → station → device → global) when eqMultiDeviceEnabled is true, and MUST fall back to the existing 2-level hierarchy (station → global) when the toggle is false. _recrearPlayer() MUST re-apply the device-resolved preset, not a stale _presetActual.
(Previously: resolution used station → global only; _recrearPlayer() re-applied _presetActual directly.)
Scenario: player recreation re-applies device-resolved preset
- GIVEN a station is playing and a device-resolved preset differs from
_presetActual - WHEN
_recrearPlayer()is called (e.g., source change) - THEN the player MUST receive the fully-resolved 4-level preset, not the stale principal
Scenario: toggle-off preserves original resolution
- GIVEN
eqMultiDeviceEnabledisfalse - WHEN a station starts playing
- THEN resolution MUST use station preset → global preset (unchanged from prior behavior)
- AND no device query MUST be made
Scenario: device subscription is set up at initialization
- GIVEN
EstadoEcualizadoris initialized witheqMultiDeviceEnabled = true - WHEN
ServicioDispositivoAudioemits a device change - THEN
EstadoEcualizadorMUST receive it and re-resolve the active preset - AND listeners MUST be notified via
notifyListeners()
Delta for export-import
MODIFIED Requirements
Requirement: Export/import schema versioned at v3
The system MUST export and import configuration at schema version 3, adding presetsPorDispositivo and presetsMatriz fields. Import of v1/v2 files MUST succeed, treating missing device fields as empty maps.
(Previously: schema was v2 with presetPrincipalEcualizador and presetsEcualizador only.)
Scenario: v3 export includes device fields
- GIVEN the user triggers a config export with device presets stored
- WHEN the export file is generated
- THEN the JSON MUST contain
"schemaVersion": 3 - AND MUST contain
"presetsPorDispositivo"as a non-null map - AND MUST contain
"presetsMatriz"as a non-null map
Scenario: v3 round-trip preserves device presets
- GIVEN a user has device and matrix presets configured
- WHEN they export and then import the same file
- THEN all device presets MUST be restored exactly
- AND all matrix presets MUST be restored exactly
Scenario: v2 file imports cleanly into v3 app
- GIVEN an export file from a previous app version (schema v2, no device fields)
- WHEN the user imports it into the v3 app
- THEN the import MUST succeed without errors
- AND
presetsDispositivoMUST default to an empty map - AND
presetsMatrizMUST default to an empty map - AND previously-existing global and station presets MUST be restored correctly
Scenario: v3 file ignored by v2 app
- GIVEN an export file from the v3 app (with device fields)
- WHEN a v2 app attempts to import it
- THEN the v2 app MUST either succeed by ignoring unknown fields, or fail with a clear version mismatch error
- AND MUST NOT corrupt or partially apply EQ state