feat(player): add radio recording and real waveform
This commit is contained in:
@@ -26,13 +26,14 @@ class ServicioRadio {
|
||||
int maxIntentos = _maxIntentosPorDefecto,
|
||||
Duration retryDelay = _retryDelayPorDefecto,
|
||||
Duration timeout = _timeoutPorDefecto,
|
||||
}) : _cliente = cliente ?? http.Client(),
|
||||
_servidores = (servidores == null || servidores.isEmpty)
|
||||
? List<String>.from(_servidoresFallback)
|
||||
: List<String>.from(servidores),
|
||||
_maxIntentos = maxIntentos < 1 ? 1 : maxIntentos,
|
||||
_retryDelay = retryDelay,
|
||||
_timeout = timeout;
|
||||
}) : _cliente = cliente ?? http.Client(),
|
||||
_servidores =
|
||||
(servidores == null || servidores.isEmpty)
|
||||
? List<String>.from(_servidoresFallback)
|
||||
: List<String>.from(servidores),
|
||||
_maxIntentos = maxIntentos < 1 ? 1 : maxIntentos,
|
||||
_retryDelay = retryDelay,
|
||||
_timeout = timeout;
|
||||
|
||||
final http.Client _cliente;
|
||||
final List<String> _servidores;
|
||||
@@ -56,10 +57,7 @@ class ServicioRadio {
|
||||
}
|
||||
|
||||
Uri _uri(String servidor, String path, Map<String, String> params) {
|
||||
return Uri.https(servidor, path, {
|
||||
'hidebroken': 'true',
|
||||
...params,
|
||||
});
|
||||
return Uri.https(servidor, path, {'hidebroken': 'true', ...params});
|
||||
}
|
||||
|
||||
Future<List<Emisora>> _get(String path, Map<String, String> params) async {
|
||||
@@ -69,15 +67,17 @@ class ServicioRadio {
|
||||
|
||||
for (int intento = 0; intento < totalIntentos; intento++) {
|
||||
final servidor = _servidorPorIntento(indiceBase, intento);
|
||||
final uri = _uri(servidor, path, {
|
||||
'lastcheckok': '1',
|
||||
...params,
|
||||
});
|
||||
final uri = _uri(servidor, path, {'lastcheckok': '1', ...params});
|
||||
|
||||
try {
|
||||
final resp = await _cliente.get(uri, headers: {
|
||||
'User-Agent': 'PluriWave/0.1.0 (es.freetimelab.pluriwave)',
|
||||
}).timeout(_timeout);
|
||||
final resp = await _cliente
|
||||
.get(
|
||||
uri,
|
||||
headers: {
|
||||
'User-Agent': 'PluriWave/0.1.0 (es.freetimelab.pluriwave)',
|
||||
},
|
||||
)
|
||||
.timeout(_timeout);
|
||||
|
||||
if (resp.statusCode != 200) {
|
||||
throw Exception('API error ${resp.statusCode}');
|
||||
@@ -85,11 +85,14 @@ class ServicioRadio {
|
||||
|
||||
final lista = json.decode(resp.body) as List<dynamic>;
|
||||
_servidorActual = servidor;
|
||||
return lista
|
||||
.cast<Map<String, dynamic>>()
|
||||
.map(Emisora.fromApi)
|
||||
.where((e) => e.uuid.isNotEmpty && e.url.isNotEmpty)
|
||||
.toList();
|
||||
final emisoras =
|
||||
lista
|
||||
.cast<Map<String, dynamic>>()
|
||||
.map(Emisora.fromApi)
|
||||
.where((e) => e.uuid.isNotEmpty && e.url.isNotEmpty)
|
||||
.toList();
|
||||
emisoras.sort(_compararCalidad);
|
||||
return emisoras;
|
||||
} on Exception catch (e) {
|
||||
ultimoError = e;
|
||||
_servidorActual = null;
|
||||
@@ -105,57 +108,78 @@ class ServicioRadio {
|
||||
}
|
||||
|
||||
/// Emisoras más votadas globalmente.
|
||||
Future<List<Emisora>> obtenerPopulares({int limit = 30, int offset = 0}) async {
|
||||
Future<List<Emisora>> obtenerPopulares({
|
||||
int limit = 30,
|
||||
int offset = 0,
|
||||
}) async {
|
||||
return _get('/json/stations/search', {
|
||||
'limit': limit.toString(),
|
||||
'offset': offset.toString(),
|
||||
'order': 'votes',
|
||||
'order': 'bitrate',
|
||||
'reverse': 'true',
|
||||
});
|
||||
}
|
||||
|
||||
/// Emisoras más escuchadas (por clicks) globalmente.
|
||||
Future<List<Emisora>> obtenerTendencias({int limit = 20}) async {
|
||||
return _get('/json/stations/topclick/$limit', {});
|
||||
final emisoras = await _get('/json/stations/topclick/$limit', {});
|
||||
emisoras.sort(_compararCalidad);
|
||||
return emisoras;
|
||||
}
|
||||
|
||||
/// Buscar por nombre de emisora.
|
||||
Future<List<Emisora>> buscarPorNombre(String query, {int limit = 30, int offset = 0}) async {
|
||||
Future<List<Emisora>> buscarPorNombre(
|
||||
String query, {
|
||||
int limit = 30,
|
||||
int offset = 0,
|
||||
}) async {
|
||||
return _get('/json/stations/search', {
|
||||
'name': query,
|
||||
'limit': limit.toString(),
|
||||
'offset': offset.toString(),
|
||||
'order': 'votes',
|
||||
'order': 'bitrate',
|
||||
'reverse': 'true',
|
||||
});
|
||||
}
|
||||
|
||||
/// Buscar por código de país (ISO 3166-1 alpha-2, e.g. 'ES', 'US').
|
||||
Future<List<Emisora>> buscarPorPais(String codigoPais, {int limit = 50, int offset = 0}) async {
|
||||
Future<List<Emisora>> buscarPorPais(
|
||||
String codigoPais, {
|
||||
int limit = 50,
|
||||
int offset = 0,
|
||||
}) async {
|
||||
return _get('/json/stations/bycountrycodeexact/$codigoPais', {
|
||||
'limit': limit.toString(),
|
||||
'offset': offset.toString(),
|
||||
'order': 'votes',
|
||||
'order': 'bitrate',
|
||||
'reverse': 'true',
|
||||
});
|
||||
}
|
||||
|
||||
/// Buscar por idioma (e.g. 'spanish', 'english').
|
||||
Future<List<Emisora>> buscarPorIdioma(String idioma, {int limit = 30, int offset = 0}) async {
|
||||
Future<List<Emisora>> buscarPorIdioma(
|
||||
String idioma, {
|
||||
int limit = 30,
|
||||
int offset = 0,
|
||||
}) async {
|
||||
return _get('/json/stations/bylanguageexact/$idioma', {
|
||||
'limit': limit.toString(),
|
||||
'offset': offset.toString(),
|
||||
'order': 'votes',
|
||||
'order': 'bitrate',
|
||||
'reverse': 'true',
|
||||
});
|
||||
}
|
||||
|
||||
/// Buscar por tag/género (e.g. 'rock', 'jazz', 'pop').
|
||||
Future<List<Emisora>> buscarPorTag(String tag, {int limit = 30, int offset = 0}) async {
|
||||
Future<List<Emisora>> buscarPorTag(
|
||||
String tag, {
|
||||
int limit = 30,
|
||||
int offset = 0,
|
||||
}) async {
|
||||
return _get('/json/stations/bytagexact/$tag', {
|
||||
'limit': limit.toString(),
|
||||
'offset': offset.toString(),
|
||||
'order': 'votes',
|
||||
'order': 'bitrate',
|
||||
'reverse': 'true',
|
||||
});
|
||||
}
|
||||
@@ -176,20 +200,36 @@ class ServicioRadio {
|
||||
if (tag != null && tag.isNotEmpty) 'tag': tag,
|
||||
'limit': limit.toString(),
|
||||
'offset': offset.toString(),
|
||||
'order': 'votes',
|
||||
'order': 'bitrate',
|
||||
'reverse': 'true',
|
||||
});
|
||||
}
|
||||
|
||||
int _compararCalidad(Emisora a, Emisora b) {
|
||||
final bitrateA = a.bitrate ?? 0;
|
||||
final bitrateB = b.bitrate ?? 0;
|
||||
final porBitrate = bitrateB.compareTo(bitrateA);
|
||||
if (porBitrate != 0) return porBitrate;
|
||||
|
||||
final porClicks = b.clickcount.compareTo(a.clickcount);
|
||||
if (porClicks != 0) return porClicks;
|
||||
|
||||
return b.votes.compareTo(a.votes);
|
||||
}
|
||||
|
||||
/// Registrar un click en la API (best effort).
|
||||
Future<void> registrarClick(String uuid) async {
|
||||
try {
|
||||
final servidor =
|
||||
_servidorActual ?? _servidorPorIntento(_indiceServidorInicial(), 0);
|
||||
await _cliente.get(
|
||||
Uri.https(servidor, '/json/url/$uuid'),
|
||||
headers: {'User-Agent': 'PluriWave/0.1.0 (es.freetimelab.pluriwave)'},
|
||||
).timeout(_timeout);
|
||||
await _cliente
|
||||
.get(
|
||||
Uri.https(servidor, '/json/url/$uuid'),
|
||||
headers: {
|
||||
'User-Agent': 'PluriWave/0.1.0 (es.freetimelab.pluriwave)',
|
||||
},
|
||||
)
|
||||
.timeout(_timeout);
|
||||
} catch (_) {
|
||||
// No crítico, ignorar.
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user