3 Commits
Author SHA1 Message Date
FreeTLab 346cd2b6b9 merge: incorporate the CI version bump
Build & Deploy PluriWave / Análisis de código (push) Successful in 26s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 1m37s
2026-08-07 00:18:49 +02:00
FreeTLab e337f6166c merge: reject a local track as a recording source 2026-08-07 00:18:48 +02:00
FreeTLab 54d87190fe fix(grabacion): reject a local track as a recording source
Reported, with this on screen:

  No se pudo iniciar la grabación: Invalid argument(s): Unsupported scheme
  'content' in URI content://com.android.externalstorage.documents/tree/
  primary%3AMusic/document/primary%3AMusic%2F...%2FNew Limit - Smile.mp3

The URI in that message is a local MP3, not a station.
PluriWaveAudioHandler._cambiarFuente sets `emisoraActual` for EVERY source
it plays, so a local track surfaces as an Emisora whose `url` is the SAF
content:// document URI it was opened from. EstadoGrabacion.iniciar only
checked for null, handed that straight to the recorder, and the HTTP
client failed with a message no user can act on.

"It used to work" is exactly right: before local music playback existed,
whatever was playing was always a real station, so the case could not
arise. The recorder never changed.

iniciar() now also requires a real network stream (esEmisoraGrabable) and
falls back to the existing "select a station first" message, which is the
correct guidance here -- recording a local file makes no sense anyway,
it is already on the device. No new l10n key, so no 13-locale churn for a
message that already says the right thing.

Tests: 1158 -> 1161.
2026-08-07 00:18:48 +02:00
2 changed files with 81 additions and 1 deletions
+24 -1
View File
@@ -18,6 +18,23 @@ import '../servicios/servicio_grabacion_radio.dart';
/// `EstadoRadio` consumers (S4-R5). Playback orchestration (stop recording on
/// pause/stop/station switch) stays in `EstadoRadio`, which keeps a reference
/// to this notifier.
/// Whether [emisora] is something the recorder can actually capture: a live
/// network stream.
///
/// The recorder opens the URL as an HTTP stream and writes the bytes to disk,
/// so anything else fails inside the HTTP client with a message no user can
/// act on ("Unsupported scheme 'content' in URI content://...").
///
/// This is not hypothetical tidiness. `PluriWaveAudioHandler._cambiarFuente`
/// sets `emisoraActual` for EVERY source it plays, so a local MP3 shows up
/// here as an `Emisora` whose `url` is the `content://` document URI it was
/// opened from. Recording a local file makes no sense anyway — it is already
/// on the device.
bool esEmisoraGrabable(Emisora emisora) {
final esquema = Uri.tryParse(emisora.url)?.scheme.toLowerCase();
return esquema == 'http' || esquema == 'https';
}
class EstadoGrabacion extends ChangeNotifier {
EstadoGrabacion({
ServicioGrabacionRadio? servicio,
@@ -72,7 +89,13 @@ class EstadoGrabacion extends ChangeNotifier {
Future<void> iniciar({Duration? duracion}) async {
final actual = _emisoraActual();
if (actual == null) {
// `emisoraActual` is set by `_cambiarFuente` for EVERY source, local
// tracks included -- a local file becomes an `Emisora` whose `url` is the
// SAF `content://` URI it was opened from. Handing that to the recorder
// produced "Unsupported scheme 'content' in URI content://..." on screen,
// and it started happening only once local music playback existed: before
// that, whatever was playing was always a real station.
if (actual == null || !esEmisoraGrabable(actual)) {
_alError?.call(_textos.recordingSelectStationFirst);
return;
}
@@ -0,0 +1,57 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:pluriwave/estado/estado_grabacion.dart';
import 'package:pluriwave/modelos/emisora.dart';
/// Reported: recording a station stopped working, with this on screen —
///
/// No se pudo iniciar la grabación: Invalid argument(s): Unsupported scheme
/// 'content' in URI content://com.android.externalstorage.documents/tree/
/// primary%3AMusic/document/primary%3AMusic%2F...%2FNew Limit - Smile.mp3
///
/// The URI in the message is a LOCAL MP3, not a station.
/// `PluriWaveAudioHandler._cambiarFuente` sets `emisoraActual` for every
/// source it plays, so a local track becomes an `Emisora` whose `url` is the
/// SAF `content://` document URI it was opened from. The recorder then tried
/// to open that as an HTTP stream.
///
/// "It used to work" is accurate: before local music playback existed,
/// whatever was playing was always a real station, so this could not happen.
void main() {
Emisora conUrl(String url) => Emisora(uuid: 'u', nombre: 'n', url: url);
test('un stream de red es grabable', () {
expect(esEmisoraGrabable(conUrl('http://stream.example.com/live')), isTrue);
expect(
esEmisoraGrabable(conUrl('https://stream.example.com/live')),
isTrue,
);
expect(
esEmisoraGrabable(conUrl('HTTPS://STREAM.EXAMPLE.COM/live')),
isTrue,
reason: 'el esquema no distingue mayúsculas',
);
});
test('la URI content:// de una pista local NO es grabable — el caso '
'exacto del reporte', () {
expect(
esEmisoraGrabable(
conUrl(
'content://com.android.externalstorage.documents/tree/'
'primary%3AMusic/document/primary%3AMusic%2FNew%20Limit%20-%20'
'Smile.mp3',
),
),
isFalse,
);
});
test('ni un fichero local, ni una url vacía o rota', () {
expect(
esEmisoraGrabable(conUrl('file:///storage/emulated/0/a.mp3')),
isFalse,
);
expect(esEmisoraGrabable(conUrl('')), isFalse);
expect(esEmisoraGrabable(conUrl('no es una uri')), isFalse);
});
}