Files
pluriwave/lib/servicios/cola_local.dart
T
FreeTLab 1d5332453f fix(audio): make the audio diagnostics visible in release builds
Every diagnostic line in the audio path used `dart:developer`'s `log()`.
That function writes to the VM service, which a RELEASE build does not
have — so in the only build that ever runs in a car, all eleven of them
went nowhere. `debugPrint`/`print` do reach logcat in release; `log()`
does not.

That includes the two channels built specifically to end the guessing:
- `registrarErrorAudioService`, which subscribes to
  `AudioService.asyncError` so the plugin's swallowed platform exceptions
  stop vanishing (b0271fa). It moved them from a dropped PublishSubject
  to a dropped log call.
- `_trazarEstadoPublicado`, the published-state trace added in 7054a4c to
  settle why the car's play button never becomes pause.

So "no evidence" was never a quiet app. It was an app writing its
evidence somewhere release builds discard. Several rounds of hypotheses
were argued without data that the app was already producing.

All eleven now use `debugPrint` with a `[PluriWave][Tag]` prefix, so one
filter catches the audio path and the existing alarm lines together:

  adb logcat | grep PluriWave

No behaviour changes. Tests: 1158, unchanged.
2026-08-06 21:48:20 +02:00

102 lines
4.5 KiB
Dart

import '../modelos/pista_local.dart';
/// Immutable snapshot of a local-music play queue (Design ADR-1): an ordered
/// list of a folder's direct audio children plus the currently-playing
/// index. Pure Dart, no side effects — every "move" method returns a NEW
/// instance rather than mutating in place, mirroring how
/// `ControladorReconexion` was extracted from `PluriWaveAudioHandler`
/// (`controlador_reconexion.dart`) so this stays fully unit-testable without
/// the handler (which cannot be instantiated in unit tests — see this
/// module's sibling test file's doc comment).
class ColaLocal {
const ColaLocal({required this.pistas, this.indice = 0});
/// The folder's direct audio children, in play order — already
/// name-sorted or Fisher-Yates shuffled by the caller before
/// construction (Design ADR-6). This class itself never re-orders them.
final List<NodoLocal> pistas;
/// The currently-playing position within [pistas].
final int indice;
/// Whether [indice] points at a real entry in [pistas] (Design "empty
/// list" edge case: an empty queue has no current track).
bool get hayActual => indice >= 0 && indice < pistas.length;
/// The track at [indice]. Only meaningful when [hayActual] is `true` —
/// callers must check it first; this getter does not guard the read
/// itself.
NodoLocal get actual => pistas[indice];
/// The queue advanced one position (Design ADR-3's advance flow):
/// `null` when [indice] is already the last position (or [pistas] is
/// empty) — end-of-queue, per Design ADR-4 (no wraparound to track 1).
ColaLocal? conSiguiente() {
final siguiente = indice + 1;
if (siguiente >= pistas.length) return null;
return ColaLocal(pistas: pistas, indice: siguiente);
}
/// The queue moved back one position, CLAMPED at 0 (Design ADR-4:
/// `skipToPrevious` at the first track restarts it instead of throwing or
/// wrapping to the last track).
ColaLocal conAnterior() {
final anterior = indice > 0 ? indice - 1 : 0;
return ColaLocal(pistas: pistas, indice: anterior);
}
}
/// Outcome of [decidirAvanceCola] (Design ADR-3): a pure decision the caller
/// (`PluriWaveAudioHandler._manejarFinPista`) acts on with
/// static-review-only field mutations — this function itself has no side
/// effects.
enum DecisionAvanceCola {
/// No advance: not a completion event, no active local queue (radio
/// isolation), or the re-entry latch is already armed (an advance is
/// already resolving its URI).
ninguna,
/// Advance to the next track — the caller marks the re-entry latch and
/// resolves/plays `colaLocal.conSiguiente()`.
avanzar,
/// The queue reached its end — the caller deactivates the queue and stops
/// playback (Design ADR-4, no wraparound).
desactivar,
}
/// Pure queue-advance decision (Design ADR-3), extracted so it is
/// unit-testable without instantiating `PluriWaveAudioHandler` — the
/// established `ControladorReconexion` precedent (see this file's class
/// doc comment). Deliberately takes NO `just_audio` type: the caller maps
/// `ProcessingState.completed` to [trackCompletado] before calling this.
///
/// Double-gated: [trackCompletado] must be `true` AND [colaLocal] must be
/// non-null (radio never sets `_colaLocal`, so a `null` queue is a
/// structural proxy for "not local-queue playback" — Design "single
/// load-bearing invariant"). [avanzandoCola] is the re-entry latch: a
/// second `completed` emission while an earlier advance is still resolving
/// its URI must be a no-op, not a double-advance.
DecisionAvanceCola decidirAvanceCola({
required ColaLocal? colaLocal,
required bool avanzandoCola,
required bool trackCompletado,
}) {
if (!trackCompletado) return DecisionAvanceCola.ninguna;
if (colaLocal == null) return DecisionAvanceCola.ninguna;
if (avanzandoCola) return DecisionAvanceCola.ninguna;
return colaLocal.conSiguiente() == null
? DecisionAvanceCola.desactivar
: DecisionAvanceCola.avanzar;
}
/// Mid-await race guard (Design ADR-3's advance flow): `true` only when
/// [siguienteEsperado] is the EXACT SAME instance still installed as
/// [colaLocalActual] — a deliberate `identical()` check, NOT `==`, so a
/// user action that replaced `_colaLocal` with a structurally-equal-but
/// distinct `ColaLocal` during the async URI-resolve gap is correctly
/// detected as stale and aborts the advance, instead of silently racing an
/// external play/stop.
bool avanceEsValido(ColaLocal? colaLocalActual, ColaLocal? siguienteEsperado) =>
identical(colaLocalActual, siguienteEsperado);