diff --git a/lib/estado/estado_grabacion.dart b/lib/estado/estado_grabacion.dart index 4572b6a..3816079 100644 --- a/lib/estado/estado_grabacion.dart +++ b/lib/estado/estado_grabacion.dart @@ -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 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; } diff --git a/test/estado/estado_grabacion_fuente_grabable_test.dart b/test/estado/estado_grabacion_fuente_grabable_test.dart new file mode 100644 index 0000000..a2509a4 --- /dev/null +++ b/test/estado/estado_grabacion_fuente_grabable_test.dart @@ -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); + }); +}