merge: make audio diagnostics visible in release builds

This commit is contained in:
2026-08-06 21:48:20 +02:00
7 changed files with 69 additions and 80 deletions
+11 -10
View File
@@ -1,5 +1,4 @@
import 'dart:async';
import 'dart:developer' as developer;
import 'package:flutter/material.dart';
@@ -94,16 +93,18 @@ StreamSubscription<Object> observarErroresAudio(
);
}
/// Default [observarErroresAudio] logger: one `[PluriWave]`-prefixed
/// `developer.log` line per swallowed plugin exception, at the same
/// `level: 900` (SEVERE) that `servicio_audio.dart`'s existing error lines
/// use, so a single logcat/DevTools filter catches both.
/// Default [observarErroresAudio] logger: one line per swallowed plugin
/// exception.
///
/// Uses [debugPrint], NOT `dart:developer`'s `log`. That distinction is the
/// whole reason this channel existed for weeks without ever producing a
/// single line of evidence: `log()` writes to the VM service, which a
/// RELEASE build does not have, so every exception this was built to catch
/// was still being thrown away — just one layer further down than before.
/// `debugPrint` reaches logcat in release, which is the only build that ever
/// runs in the car.
void registrarErrorAudioService(Object error) {
developer.log(
'[PluriWave] AudioService.asyncError: $error',
name: 'ArranqueAudio',
level: 900,
);
debugPrint('[PluriWave][ArranqueAudio] AudioService.asyncError: $error');
}
/// Minimal branded bootstrap widget for the degraded path (Design "still
+2 -4
View File
@@ -97,7 +97,5 @@ DecisionAvanceCola decidirAvanceCola({
/// 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);
bool avanceEsValido(ColaLocal? colaLocalActual, ColaLocal? siguienteEsperado) =>
identical(colaLocalActual, siguienteEsperado);
+7 -7
View File
@@ -61,9 +61,10 @@ String nombreCarpetaDesdeUri(String treeUri, {required String nombreGenerico}) {
segmento = documentId.substring(ultimaBarra + 1);
} else {
final ultimosDosPuntos = documentId.lastIndexOf(':');
segmento = ultimosDosPuntos >= 0
? documentId.substring(ultimosDosPuntos + 1)
: documentId;
segmento =
ultimosDosPuntos >= 0
? documentId.substring(ultimosDosPuntos + 1)
: documentId;
}
final recortado = segmento.trim();
@@ -200,10 +201,9 @@ class FuenteMusicaLocalAutoImpl implements FuenteMusicaLocalAuto {
try {
final uri = await _uriPersistida();
if (uri == null || uri.isEmpty) return false;
final valido = await _canal.invokeMethod<bool>(
'hasPersistedPermission',
{'treeUri': uri},
);
final valido = await _canal.invokeMethod<bool>('hasPersistedPermission', {
'treeUri': uri,
});
return valido ?? false;
} catch (_) {
// Cold-start / revoked-permission safety (Spec "Permission revoked or
+22 -36
View File
@@ -1,9 +1,8 @@
import 'dart:async';
import 'dart:developer' as developer;
import 'dart:ui' show Locale;
import 'package:audio_service/audio_service.dart';
import 'package:flutter/foundation.dart' show visibleForTesting;
import 'package:flutter/foundation.dart' show debugPrint, visibleForTesting;
import 'package:just_audio/just_audio.dart';
import '../l10n/display_names.dart';
@@ -836,7 +835,14 @@ class PluriWaveAudioHandler extends BaseAudioHandler
/// `eqDisponible` rides along because the equalizer custom action is gated
/// on it and the flag is otherwise unobservable — one car session answers
/// both questions at once:
/// adb logcat -s ServicioAudio
///
/// adb logcat | grep PluriWave
///
/// It uses [debugPrint] and NOT `dart:developer`'s `log`, and that is not a
/// style choice. `log()` writes to the VM service, which a RELEASE build
/// does not have — so this trace, and every error line in this file, was
/// invisible in the only build that ever runs in a car. Weeks of "no
/// evidence" were this, not a quiet app. Do not convert these back.
void _trazarEstadoPublicado() {
final s = playbackState.value;
final traza =
@@ -846,7 +852,7 @@ class PluriWaveAudioHandler extends BaseAudioHandler
'controles=${s.controls.length}';
if (traza == _ultimaTrazaEstado) return;
_ultimaTrazaEstado = traza;
developer.log(traza, name: 'ServicioAudio');
debugPrint('[PluriWave][ServicioAudio] estado $traza');
}
/// Binds [construirControlesTransporte] — which holds the whole contract,
@@ -909,11 +915,7 @@ class PluriWaveAudioHandler extends BaseAudioHandler
mensaje = _textos.audioErrorGeneric;
}
developer.log(
'[PluriWave] Error reproducción: $codigoLog',
name: 'ServicioAudio',
level: 900,
);
debugPrint('[PluriWave][ServicioAudio] Error reproducción: $codigoLog');
_detenerReconexion();
playbackState.add(
@@ -956,12 +958,10 @@ class PluriWaveAudioHandler extends BaseAudioHandler
}
_reconectando = true;
developer.log(
'[PluriWave] Stall de red, reintento ${_reconexion.intentos}/'
debugPrint(
'[PluriWave][ServicioAudio] Stall de red, reintento ${_reconexion.intentos}/'
'${_reconexion.maxReintentos} en '
'${_reconexion.retrasoParaIntento(_reconexion.intentos).inSeconds}s',
name: 'ServicioAudio',
level: 800,
);
playbackState.add(
playbackState.value.copyWith(
@@ -1207,11 +1207,8 @@ class PluriWaveAudioHandler extends BaseAudioHandler
rethrow;
} on Exception catch (e, stackTrace) {
_cambiandoFuente = false;
developer.log(
'[PluriWave] Error inesperado en playMediaItem: $e',
name: 'ServicioAudio',
level: 900,
stackTrace: stackTrace,
debugPrint(
'[PluriWave][ServicioAudio] Error inesperado en playMediaItem: $e',
);
if (revision == _revisionFuente) {
playbackState.add(
@@ -1264,11 +1261,8 @@ class PluriWaveAudioHandler extends BaseAudioHandler
void _iniciarPlaySinBloquear(MediaItem mediaItem, int revision) {
unawaited(
_player.play().catchError((Object error, StackTrace stackTrace) {
developer.log(
'[PluriWave] Error al iniciar ${mediaItem.title}: $error',
name: 'ServicioAudio',
level: 900,
stackTrace: stackTrace,
debugPrint(
'[PluriWave][ServicioAudio] Error al iniciar ${mediaItem.title}: $error',
);
if (revision == _revisionFuente) {
_gestionarErrorReproduccion(error);
@@ -1553,11 +1547,7 @@ class PluriWaveAudioHandler extends BaseAudioHandler
if (destino == null) return;
await playMediaItem(mediaItemParaEmisora(destino, l10n: _textos));
} catch (e) {
developer.log(
'[PluriWave] Error saltando de emisora: $e',
name: 'ServicioAudio',
level: 900,
);
debugPrint('[PluriWave][ServicioAudio] Error saltando de emisora: $e');
}
}
@@ -1720,10 +1710,8 @@ class PluriWaveAudioHandler extends BaseAudioHandler
if (emisora == null) return;
await playMediaItem(mediaItemParaEmisora(emisora, l10n: _textos));
} catch (e) {
developer.log(
'[PluriWave] Error en playFromSearch($query): $e',
name: 'ServicioAudio',
level: 900,
debugPrint(
'[PluriWave][ServicioAudio] Error en playFromSearch($query): $e',
);
}
}
@@ -1793,10 +1781,8 @@ class PluriWaveAudioHandler extends BaseAudioHandler
);
} catch (e) {
// Spec "Unknown or stale media id": never propagate from the handler.
developer.log(
'[PluriWave] Error en playFromMediaId($mediaId): $e',
name: 'ServicioAudio',
level: 900,
debugPrint(
'[PluriWave][ServicioAudio] Error en playFromMediaId($mediaId): $e',
);
}
}
+2 -5
View File
@@ -1,5 +1,4 @@
import 'dart:async';
import 'dart:developer' as developer;
import 'package:audio_session/audio_session.dart';
import 'package:flutter/foundation.dart';
@@ -89,10 +88,8 @@ class ServicioAudioSession {
(_) => unawaited(manejarDesconexionSalida()),
);
} catch (e) {
developer.log(
'[PluriWave] No se pudo configurar la sesion de audio: $e',
name: 'ServicioAudioSession',
level: 900,
debugPrint(
'[PluriWave][ServicioAudioSession] No se pudo configurar la sesion de audio: $e',
);
}
}
@@ -157,7 +157,9 @@ class ServicioDispositivoAudioReal extends ServicioDispositivoAudio {
@override
Future<Map<String, String>> obtenerNombresEmparejados() async {
try {
final raw = await _methodChannel.invokeMethod<Map>('getBondedDeviceNames');
final raw = await _methodChannel.invokeMethod<Map>(
'getBondedDeviceNames',
);
if (raw == null) return const {};
return {
for (final entry in raw.entries)
+22 -17
View File
@@ -70,7 +70,10 @@ class ServicioEcualizador {
final porEmisora = _leerPresetsPorEmisora(prefs);
final presetsDispositivo = _leerMapa(prefs, _keyPresetsPorDispositivo);
final presetsMatriz = _leerMapa(prefs, _keyPresetsMatriz);
final nombresDispositivos = _leerMapaStrings(prefs, _keyNombresDispositivos);
final nombresDispositivos = _leerMapaStrings(
prefs,
_keyNombresDispositivos,
);
return ConfiguracionEcualizador(
principal: principal,
porEmisora: porEmisora,
@@ -124,21 +127,22 @@ class ServicioEcualizador {
String prefijo,
) async {
final presetsPorDispositivo = _leerMapa(prefs, _keyPresetsPorDispositivo);
final dispositivos = presetsPorDispositivo.keys
.where((clave) => clave.startsWith(prefijo))
.toList();
final dispositivos =
presetsPorDispositivo.keys
.where((clave) => clave.startsWith(prefijo))
.toList();
final nombres = _leerMapaStrings(prefs, _keyNombresDispositivos);
final nombresAPurgar = nombres.keys
.where((clave) => clave.startsWith(prefijo))
.toList();
final nombresAPurgar =
nombres.keys.where((clave) => clave.startsWith(prefijo)).toList();
final matriz = _leerMapa(prefs, _keyPresetsMatriz);
final matrizAPurgar = matriz.keys.where((clave) {
final separador = clave.indexOf(':');
if (separador == -1) return false;
return clave.substring(separador + 1).startsWith(prefijo);
}).toList();
final matrizAPurgar =
matriz.keys.where((clave) {
final separador = clave.indexOf(':');
if (separador == -1) return false;
return clave.substring(separador + 1).startsWith(prefijo);
}).toList();
for (final clave in dispositivos) {
presetsPorDispositivo.remove(clave);
@@ -183,11 +187,12 @@ class ServicioEcualizador {
// (station UUIDs are RFC4122 and contain no colons — multi-device-eq
// ADR-3), since deviceId itself may contain colons (e.g. a MAC-based id).
final presetsMatriz = _leerMapa(prefs, _keyPresetsMatriz);
final clavesMatrizAPurgar = presetsMatriz.keys.where((clave) {
final separador = clave.indexOf(':');
if (separador == -1) return false;
return clave.substring(separador + 1) == deviceId;
}).toList();
final clavesMatrizAPurgar =
presetsMatriz.keys.where((clave) {
final separador = clave.indexOf(':');
if (separador == -1) return false;
return clave.substring(separador + 1) == deviceId;
}).toList();
if (clavesMatrizAPurgar.isNotEmpty) {
for (final clave in clavesMatrizAPurgar) {
presetsMatriz.remove(clave);