fix(audio): keep custom actions out of the media notification controls

The equalizer toggle appended to the transport controls was aborting the
whole notification. controls feeds BOTH the phone notification and the
car playback screen, and AudioService.setState walks every control
through createCustomAction (AudioService.java:513-520) BEFORE reaching
mediaSession.setPlaybackState (:552) and enterPlayingState (:559) -- the
only place the notification is ever posted.

createCustomAction resolves the icon by name via getIdentifier (:415-420),
which returns 0 on a miss, and passes it to
PlaybackStateCompat.CustomAction.Builder, which throws on a 0 icon or an
empty label. That throw aborts setState, so the media session is never
published: no shade widget, no lock-screen controls, not even the small
status-bar icon. ExoPlayer runs independently so audio keeps playing, and
until asyncError got a subscriber the exception was dropped silently.

Nothing is lost in the car: the Ecualizador browse folder already lists
Desactivar plus every preset by name, which is Auto's own idiom for
choosing among options.
This commit is contained in:
2026-08-01 20:33:34 +02:00
parent abc6b47ffb
commit cacd3ece57
2 changed files with 124 additions and 15 deletions
+31 -15
View File
@@ -637,6 +637,29 @@ class PluriWaveAudioHandler extends BaseAudioHandler
/// [MediaControl.skipToPrevious]/play-pause/stop/[MediaControl.skipToNext]
/// at their existing indices 0-3, so `androidCompactActionIndices`
/// (`[colaActiva ? 1 : 0]`) stays correct unchanged.
/// NOTHING custom goes in this list. `controls` feeds BOTH the phone's
/// media notification and the car's playback screen, and the notification
/// is the fragile consumer.
///
/// `AudioService.setState` (AudioService.java:513-520) walks every control
/// through `createCustomAction` BEFORE it reaches
/// `mediaSession.setPlaybackState` (:552) and `enterPlayingState()` (:559),
/// which is the ONLY place the notification is ever posted.
/// `createCustomAction` resolves the icon by name via
/// `getResources().getIdentifier(...)` (:415-420) — which returns 0 on a
/// miss — and hands it to `PlaybackStateCompat.CustomAction.Builder`, which
/// throws on a 0 icon or an empty label. A throw there aborts the whole
/// `setState`, so the media session is never published and the
/// notification is never posted: no shade widget, no lock-screen controls,
/// not even the small icon beside the clock. Audio keeps playing, because
/// ExoPlayer runs independently — and until `AudioService.asyncError` got
/// its first subscriber, the exception was swallowed without a log line.
///
/// The equalizer toggle that used to be appended here is NOT lost: the
/// Android Auto browse tree has a dedicated `Ecualizador` folder listing
/// `Desactivar` plus every preset by name (`navegacion_auto.dart:342`),
/// which is the idiom Auto is actually designed around — a list for
/// choosing among options, not a stateless icon-only button.
List<MediaControl> _controlesTransporte({
required bool colaActiva,
required bool playing,
@@ -645,23 +668,16 @@ class PluriWaveAudioHandler extends BaseAudioHandler
if (playing) MediaControl.pause else MediaControl.play,
MediaControl.stop,
if (colaActiva) MediaControl.skipToNext,
..._controlesEqPersonalizados(),
];
List<MediaControl> _controlesEqPersonalizados() =>
controlesEcualizadorPersonalizados(
disponible: _eqDisponible,
activo: _ecualizadorActivo,
l10n: _textos,
);
/// Re-pushes `playbackState` with a freshly built controls list (item 4):
/// called whenever EQ availability/enabled/preset state changes outside a
/// player-state transition (a custom-action tap, or a phone-side preset/
/// toggle change), so the equalizer custom actions' label and current-
/// preset name stay in sync on the now-playing screen without waiting for
/// an unrelated player event. Idempotent and cheap (no native calls) —
/// safe to call from any EQ state-changing path.
/// Re-pushes `playbackState` with a freshly built controls list.
///
/// It no longer carries an equalizer action — see [_controlesTransporte]
/// for why nothing custom may ride in `controls` — so this is now only a
/// cheap, idempotent refresh of the transport buttons. Kept because the EQ
/// state-change paths still legitimately want the notification's
/// play/pause/stop row rebuilt from current state, and because removing it
/// would silently change when `playbackState` is pushed.
void _actualizarControlesEq() {
playbackState.add(
playbackState.value.copyWith(