feat(player): add radio recording and real waveform
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:http/testing.dart';
|
||||
import 'package:pluriwave/modelos/emisora.dart';
|
||||
import 'package:pluriwave/servicios/servicio_grabacion_radio.dart';
|
||||
|
||||
void main() {
|
||||
group('ServicioGrabacionRadio', () {
|
||||
test(
|
||||
'guarda el stream original en disco con extensión por codec',
|
||||
() async {
|
||||
final dir = await Directory.systemTemp.createTemp('pluriwave-rec-');
|
||||
final servicio = ServicioGrabacionRadio(
|
||||
cliente: MockClient((request) async {
|
||||
return http.Response.bytes(
|
||||
[1, 2, 3, 4, 5],
|
||||
200,
|
||||
headers: {'content-type': 'audio/mpeg'},
|
||||
);
|
||||
}),
|
||||
resolverDirectorioBase: () async => dir,
|
||||
reloj: () => DateTime(2026, 5, 21, 18, 30),
|
||||
);
|
||||
|
||||
await servicio.iniciar(
|
||||
const Emisora(
|
||||
uuid: 'r1',
|
||||
nombre: 'Radio Prueba',
|
||||
url: 'https://stream.example/radio',
|
||||
codec: 'MP3',
|
||||
),
|
||||
);
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
|
||||
final carpeta = Directory(
|
||||
'${dir.path}${Platform.pathSeparator}grabaciones',
|
||||
);
|
||||
final archivos = await carpeta.list().where((e) => e is File).toList();
|
||||
expect(archivos, hasLength(1));
|
||||
expect(archivos.single.path, endsWith('.mp3'));
|
||||
expect(await File(archivos.single.path).readAsBytes(), [1, 2, 3, 4, 5]);
|
||||
expect(servicio.estado.tipo, EstadoGrabacionRadioTipo.inactiva);
|
||||
|
||||
await servicio.dispose();
|
||||
},
|
||||
);
|
||||
|
||||
test('detiene una grabación activa bajo demanda', () async {
|
||||
final dir = await Directory.systemTemp.createTemp('pluriwave-rec-stop-');
|
||||
final controller = StreamController<List<int>>();
|
||||
final servicio = ServicioGrabacionRadio(
|
||||
cliente: _StreamClient(controller.stream),
|
||||
resolverDirectorioBase: () async => dir,
|
||||
);
|
||||
|
||||
await servicio.iniciar(
|
||||
const Emisora(
|
||||
uuid: 'r2',
|
||||
nombre: 'Radio Larga',
|
||||
url: 'https://stream.example/live',
|
||||
codec: 'aac',
|
||||
),
|
||||
);
|
||||
controller.add([10, 20, 30]);
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
|
||||
expect(servicio.estado.activa, isTrue);
|
||||
expect(servicio.estado.bytes, 3);
|
||||
|
||||
await servicio.detener();
|
||||
|
||||
expect(servicio.estado.tipo, EstadoGrabacionRadioTipo.inactiva);
|
||||
await controller.close();
|
||||
await servicio.dispose();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class _StreamClient extends http.BaseClient {
|
||||
_StreamClient(this.stream);
|
||||
|
||||
final Stream<List<int>> stream;
|
||||
|
||||
@override
|
||||
Future<http.StreamedResponse> send(http.BaseRequest request) async {
|
||||
return http.StreamedResponse(
|
||||
stream,
|
||||
200,
|
||||
headers: {'content-type': 'audio/aac'},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -8,47 +8,48 @@ import 'package:pluriwave/servicios/servicio_radio.dart';
|
||||
void main() {
|
||||
group('ServicioRadio retry + rotación', () {
|
||||
test(
|
||||
'reintenta con otro host cuando el primero falla y recupera en el segundo',
|
||||
() async {
|
||||
final hostsSolicitados = <String>[];
|
||||
final servicio = ServicioRadio(
|
||||
cliente: MockClient((request) async {
|
||||
hostsSolicitados.add(request.url.host);
|
||||
if (request.url.host == 'host-1.api.radio-browser.info') {
|
||||
return http.Response('fallo', 500);
|
||||
}
|
||||
return http.Response(
|
||||
jsonEncode([
|
||||
{
|
||||
'stationuuid': 'uuid-ok',
|
||||
'name': 'Radio Recuperada',
|
||||
'url_resolved': 'https://stream.recuperada/audio',
|
||||
},
|
||||
]),
|
||||
200,
|
||||
headers: {'content-type': 'application/json'},
|
||||
);
|
||||
}),
|
||||
servidores: const [
|
||||
'host-1.api.radio-browser.info',
|
||||
'host-2.api.radio-browser.info',
|
||||
],
|
||||
maxIntentos: 3,
|
||||
retryDelay: Duration.zero,
|
||||
);
|
||||
'reintenta con otro host cuando el primero falla y recupera en el segundo',
|
||||
() async {
|
||||
final hostsSolicitados = <String>[];
|
||||
final servicio = ServicioRadio(
|
||||
cliente: MockClient((request) async {
|
||||
hostsSolicitados.add(request.url.host);
|
||||
if (request.url.host == 'host-1.api.radio-browser.info') {
|
||||
return http.Response('fallo', 500);
|
||||
}
|
||||
return http.Response(
|
||||
jsonEncode([
|
||||
{
|
||||
'stationuuid': 'uuid-ok',
|
||||
'name': 'Radio Recuperada',
|
||||
'url_resolved': 'https://stream.recuperada/audio',
|
||||
},
|
||||
]),
|
||||
200,
|
||||
headers: {'content-type': 'application/json'},
|
||||
);
|
||||
}),
|
||||
servidores: const [
|
||||
'host-1.api.radio-browser.info',
|
||||
'host-2.api.radio-browser.info',
|
||||
],
|
||||
maxIntentos: 3,
|
||||
retryDelay: Duration.zero,
|
||||
);
|
||||
|
||||
final emisoras = await servicio.obtenerPopulares(limit: 1);
|
||||
final emisoras = await servicio.obtenerPopulares(limit: 1);
|
||||
|
||||
expect(emisoras, hasLength(1));
|
||||
expect(emisoras.first.uuid, 'uuid-ok');
|
||||
expect(
|
||||
hostsSolicitados,
|
||||
equals([
|
||||
'host-1.api.radio-browser.info',
|
||||
'host-2.api.radio-browser.info',
|
||||
]),
|
||||
);
|
||||
});
|
||||
expect(emisoras, hasLength(1));
|
||||
expect(emisoras.first.uuid, 'uuid-ok');
|
||||
expect(
|
||||
hostsSolicitados,
|
||||
equals([
|
||||
'host-1.api.radio-browser.info',
|
||||
'host-2.api.radio-browser.info',
|
||||
]),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test('corta al llegar al tope de intentos y propaga error final', () async {
|
||||
var intentos = 0;
|
||||
@@ -68,5 +69,40 @@ void main() {
|
||||
);
|
||||
expect(intentos, 2);
|
||||
});
|
||||
|
||||
test('prioriza emisoras verificadas de mayor bitrate', () async {
|
||||
final servicio = ServicioRadio(
|
||||
cliente: MockClient((request) async {
|
||||
expect(request.url.queryParameters['order'], 'bitrate');
|
||||
expect(request.url.queryParameters['reverse'], 'true');
|
||||
return http.Response(
|
||||
jsonEncode([
|
||||
{
|
||||
'stationuuid': 'baja',
|
||||
'name': 'Baja',
|
||||
'url_resolved': 'https://stream.example/low',
|
||||
'bitrate': 64,
|
||||
'votes': 999,
|
||||
},
|
||||
{
|
||||
'stationuuid': 'alta',
|
||||
'name': 'Alta',
|
||||
'url_resolved': 'https://stream.example/high',
|
||||
'bitrate': 320,
|
||||
'votes': 1,
|
||||
},
|
||||
]),
|
||||
200,
|
||||
headers: {'content-type': 'application/json'},
|
||||
);
|
||||
}),
|
||||
servidores: const ['host.api.radio-browser.info'],
|
||||
retryDelay: Duration.zero,
|
||||
);
|
||||
|
||||
final emisoras = await servicio.buscar(nombre: 'radio');
|
||||
|
||||
expect(emisoras.map((e) => e.uuid), equals(['alta', 'baja']));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user