Files
pluriwave/test/helpers/fakes.dart
T
FreeTLab 4ca2813267 feat(ecualizador): include equalizer on/off toggle in export/import
The backup envelope carried favorites, EQ presets, alarms and the
multi-device toggle but not EstadoEcualizador's own on/off flag, so
restoring a backup on another device silently kept that device's
existing toggle state instead of the source device's.

Bumps the backup schema to v4 (additive over v3): the flag is only
written when explicitly provided, so old exports stay at v2/v3.
Importing an old backup without the field leaves the current toggle
untouched rather than defaulting it. Applying the imported value
reuses EstadoEcualizador.cambiarActivo so it persists and pushes to
the live audio engine exactly like a manual toggle.
2026-08-28 23:07:01 +02:00

760 lines
24 KiB
Dart

import 'dart:async';
import 'package:pluriwave/l10n/gen/app_localizations.dart';
import 'package:pluriwave/modelos/dispositivo_audio.dart';
import 'package:pluriwave/modelos/emisora.dart';
import 'package:pluriwave/modelos/grupo_favoritos.dart';
import 'package:pluriwave/modelos/pais_radio.dart';
import 'package:pluriwave/modelos/preset_ecualizador.dart';
import 'package:pluriwave/servicios/servicio_audio.dart';
import 'package:pluriwave/servicios/servicio_dispositivo_audio.dart';
import 'package:pluriwave/servicios/servicio_ecualizador.dart';
import 'package:pluriwave/servicios/servicio_favoritos.dart';
import 'package:pluriwave/servicios/servicio_grabacion_radio.dart';
import 'package:pluriwave/servicios/servicio_presets_personalizados.dart';
import 'package:pluriwave/servicios/servicio_radio.dart';
class FakeServicioAudio extends ServicioAudio {
FakeServicioAudio({this.ecualizadorDisponible = true}) {
_estadoController.add(EstadoReproduccion.detenido);
}
/// Whether the native equalizer is available on this device — NOT whether
/// it is currently switched on (see [ecualizadorActivo] for that).
@override
final bool ecualizadorDisponible;
final _estadoController = StreamController<EstadoReproduccion>.broadcast();
final List<PresetEcualizador> presetsAplicados = [];
final List<Emisora> emisorasReproducidas = [];
final List<bool> cambiosEcualizadorActivo = [];
final List<double> volumenesAplicados = [];
int pausas = 0;
int configuracionesL10n = 0;
Emisora? _emisoraActual;
EstadoReproduccion _estadoActual = EstadoReproduccion.detenido;
/// Mirrors `PluriWaveAudioHandler._ecualizadorActivo`/`_presetActual`:
/// the handler-side EQ state, settable independently of the
/// `ServicioAudio`-forwarded methods below so tests can simulate a
/// car/notification-initiated change (eq-sync-superficies).
bool _ecualizadorActivoValor = true;
PresetEcualizador _presetActualValor = PresetEcualizador.flat;
@override
bool get ecualizadorActivo => _ecualizadorActivoValor;
@override
PresetEcualizador get presetActual => _presetActualValor;
/// Simulates a car/notification-initiated EQ change: mutates the (fake)
/// handler's own state directly, the same way
/// `PluriWaveAudioHandler.customAction`/`seleccionarPresetEqPorMediaId`
/// call `setEcualizadorActivo`/`aplicarPreset` on the handler WITHOUT
/// going through `ServicioAudio` — then re-emits the current playback
/// state, mirroring `_actualizarControlesEq()`'s unconditional
/// `playbackState.add(...)` republish so a resync listener on
/// [estadoStream] picks it up. Deliberately does NOT append to
/// [cambiosEcualizadorActivo]/[presetsAplicados]: those track calls that
/// arrived through the `ServicioAudio`-forwarded (UI-initiated) path.
void simularCambioEqDesdeHandler({bool? activo, PresetEcualizador? preset}) {
if (activo != null) _ecualizadorActivoValor = activo;
if (preset != null) _presetActualValor = preset;
emitirEstado(_estadoActual);
}
@override
void configurarLocalizaciones(AppLocalizations l10n) {
// No global handler in tests; just record the call.
configuracionesL10n++;
}
@override
Emisora? get emisoraActual => _emisoraActual;
@override
Stream<EstadoReproduccion> get estadoStream => _estadoController.stream;
// WU5: the real getter needs registrarHandler() (main.dart, production
// only) — reading it against a bare FakeServicioAudio throws
// "registrarHandler() no fue llamado en main.dart". No test exercised
// this getter before PantallaInicio started wiring VisualizadorAudio to
// it (WU5's Escuchar hero, mirroring pantalla_reproductor.dart's own
// usage, which has no test file at all). An empty stream matches
// VisualizadorAudio's own documented no-native-session fallback.
@override
Stream<int?> get androidAudioSessionIdStream => const Stream<int?>.empty();
@override
bool get estaSonando => _estadoActual == EstadoReproduccion.reproduciendo;
@override
Future<void> reproducir(Emisora emisora) async {
_emisoraActual = emisora;
emisorasReproducidas.add(emisora);
emitirEstado(EstadoReproduccion.reproduciendo);
}
@override
Future<void> detener() async {
_emisoraActual = null;
emitirEstado(EstadoReproduccion.detenido);
}
@override
Future<void> pausar() async {
pausas++;
emitirEstado(EstadoReproduccion.pausado);
}
// WU14: the real ServicioAudio.togglePlay() reads `_handler.playbackState`
// (a real just_audio-backed handler that requires registrarHandler(), same
// gap already documented for androidAudioSessionIdStream above) — unsafe
// against a bare FakeServicioAudio. Overridden here using only this Fake's
// own state machinery so `pantalla_reproductor.dart`'s play/pause control
// (previously untested) can be exercised safely.
@override
Future<void> togglePlay() async {
if (_estadoActual == EstadoReproduccion.reproduciendo) {
await pausar();
} else {
emitirEstado(EstadoReproduccion.reproduciendo);
}
}
@override
Future<void> setVolumen(double vol) async {
volumenesAplicados.add(vol);
}
void emitirEstado(EstadoReproduccion estado) {
_estadoActual = estado;
_estadoController.add(estado);
}
@override
Future<void> aplicarPreset(PresetEcualizador preset) async {
presetsAplicados.add(preset);
_presetActualValor = preset;
}
@override
Future<void> setBanda(int index, double db) async {}
@override
Future<void> setEcualizadorActivo(bool activo) async {
cambiosEcualizadorActivo.add(activo);
_ecualizadorActivoValor = activo;
}
@override
Future<void> dispose() async {
await _estadoController.close();
}
}
class FakeServicioFavoritos extends ServicioFavoritos {
final List<Emisora> _favoritos = [];
final List<GrupoFavoritos> _grupos = [
const GrupoFavoritos(
id: GrupoFavoritos.sinAsignarId,
nombre: 'Sin asignar',
orden: 0,
protegido: true,
),
];
int toggleCalls = 0;
@override
Future<List<Emisora>> obtenerTodos() async =>
_favoritos.map((e) => e.copyWith()).toList();
@override
Future<void> agregar(Emisora emisora) async {
_favoritos.removeWhere((e) => e.uuid == emisora.uuid);
_favoritos.add(emisora.copyWith(orden: _favoritos.length));
}
@override
Future<void> eliminar(String uuid) async {
_favoritos.removeWhere((e) => e.uuid == uuid);
for (var i = 0; i < _favoritos.length; i++) {
_favoritos[i] = _favoritos[i].copyWith(orden: i);
}
}
@override
Future<bool> esFavorito(String uuid) async =>
_favoritos.any((e) => e.uuid == uuid);
@override
Future<bool> toggleFavorito(Emisora emisora) async {
toggleCalls += 1;
if (_favoritos.any((e) => e.uuid == emisora.uuid)) {
await eliminar(emisora.uuid);
return false;
}
await agregar(emisora);
return true;
}
@override
Future<List<GrupoFavoritos>> obtenerGrupos() async =>
List.unmodifiable(_grupos);
@override
Future<GrupoFavoritos> crearGrupo(String nombre) async {
final grupo = GrupoFavoritos(
id: 'grupo_${_grupos.length}',
nombre: nombre.trim(),
orden: _grupos.length,
);
_grupos.add(grupo);
return grupo;
}
@override
Future<void> renombrarGrupo(String id, String nombre) async {
final index = _grupos.indexWhere((g) => g.id == id && !g.protegido);
if (index == -1) return;
_grupos[index] = _grupos[index].copyWith(nombre: nombre.trim());
}
@override
Future<void> eliminarGrupo(String id) async {
if (id == GrupoFavoritos.sinAsignarId) return;
_grupos.removeWhere((g) => g.id == id && !g.protegido);
for (var i = 0; i < _favoritos.length; i++) {
if (_favoritos[i].grupoFavoritosId == id) {
_favoritos[i] = _favoritos[i].copyWith(
grupoFavoritosId: GrupoFavoritos.sinAsignarId,
);
}
}
}
@override
Future<void> asignarGrupo(String uuid, String grupoId) async {
final destino =
_grupos.any((g) => g.id == grupoId)
? grupoId
: GrupoFavoritos.sinAsignarId;
final index = _favoritos.indexWhere((e) => e.uuid == uuid);
if (index != -1) {
_favoritos[index] = _favoritos[index].copyWith(grupoFavoritosId: destino);
}
}
@override
Future<void> reordenar(String uuid, int nuevoOrden) async {
final oldIndex = _favoritos.indexWhere((e) => e.uuid == uuid);
if (oldIndex == -1 || _favoritos.isEmpty) return;
final targetIndex = nuevoOrden.clamp(0, _favoritos.length - 1);
final moved = _favoritos.removeAt(oldIndex);
_favoritos.insert(targetIndex, moved);
for (var i = 0; i < _favoritos.length; i++) {
_favoritos[i] = _favoritos[i].copyWith(orden: i);
}
}
}
class FakeServicioRadio extends ServicioRadio {
FakeServicioRadio({
List<Emisora>? populares,
List<Emisora>? tendencias,
List<Emisora>? busqueda,
List<List<Emisora>>? popularesPorLlamada,
List<List<Emisora>>? tendenciasPorLlamada,
List<Object>? erroresPopularesPorLlamada,
List<Object>? erroresTendenciasPorLlamada,
List<PaisRadio>? paises,
Object? errorPaises,
}) : _populares = populares ?? [],
_tendencias = tendencias ?? [],
_busqueda = busqueda ?? [],
_popularesPorLlamada = popularesPorLlamada ?? const [],
_tendenciasPorLlamada = tendenciasPorLlamada ?? const [],
_erroresPopularesPorLlamada = erroresPopularesPorLlamada ?? const [],
_erroresTendenciasPorLlamada = erroresTendenciasPorLlamada ?? const [],
_paises = paises ?? [],
_errorPaises = errorPaises;
final List<Emisora> _populares;
final List<Emisora> _tendencias;
final List<Emisora> _busqueda;
final List<List<Emisora>> _popularesPorLlamada;
final List<List<Emisora>> _tendenciasPorLlamada;
final List<Object> _erroresPopularesPorLlamada;
final List<Object> _erroresTendenciasPorLlamada;
final List<PaisRadio> _paises;
final Object? _errorPaises;
int obtenerPopularesCalls = 0;
int obtenerTendenciasCalls = 0;
int registrarClickCalls = 0;
int buscarCalls = 0;
int obtenerPaisesCalls = 0;
String? ultimoUuidClick;
Exception _normalizarError(Object error) =>
error is Exception ? error : Exception(error.toString());
@override
Future<List<Emisora>> obtenerPopulares({
int limit = 30,
int offset = 0,
}) async {
final llamada = obtenerPopularesCalls++;
if (llamada < _erroresPopularesPorLlamada.length) {
throw _normalizarError(_erroresPopularesPorLlamada[llamada]);
}
final data =
llamada < _popularesPorLlamada.length
? _popularesPorLlamada[llamada]
: _populares;
return data.take(limit).toList();
}
@override
Future<List<Emisora>> obtenerTendencias({int limit = 20}) async {
final llamada = obtenerTendenciasCalls++;
if (llamada < _erroresTendenciasPorLlamada.length) {
throw _normalizarError(_erroresTendenciasPorLlamada[llamada]);
}
final data =
llamada < _tendenciasPorLlamada.length
? _tendenciasPorLlamada[llamada]
: _tendencias;
return data.take(limit).toList();
}
@override
Future<List<Emisora>> buscar({
String? nombre,
String? pais,
String? idioma,
String? tag,
int limit = 30,
int offset = 0,
}) async {
// WU6: counts calls so tests can assert a client-side re-sort does NOT
// trigger a new network request (`station-discovery-browse` spec,
// "Client-Side Search Sort Only").
buscarCalls++;
return _busqueda.skip(offset).take(limit).toList();
}
@override
Future<void> registrarClick(String uuid) async {
registrarClickCalls += 1;
ultimoUuidClick = uuid;
}
@override
Future<List<PaisRadio>> obtenerPaises() async {
obtenerPaisesCalls++;
final error = _errorPaises;
if (error != null) throw _normalizarError(error);
return _paises;
}
}
class FakeServicioEcualizador extends ServicioEcualizador {
FakeServicioEcualizador({
PresetEcualizador? principal,
Map<String, PresetEcualizador>? porEmisora,
bool activo = true,
bool eqMultiDeviceEnabled = false,
Map<String, PresetEcualizador>? presetsDispositivo,
Map<String, PresetEcualizador>? presetsMatriz,
Map<String, String>? nombresDispositivos,
}) : _config = ConfiguracionEcualizador(
principal: principal ?? PresetEcualizador.flat,
porEmisora: porEmisora ?? {},
activo: activo,
eqMultiDeviceEnabled: eqMultiDeviceEnabled,
presetsDispositivo: presetsDispositivo ?? {},
presetsMatriz: presetsMatriz ?? {},
nombresDispositivos: nombresDispositivos ?? {},
);
ConfiguracionEcualizador _config;
ConfiguracionEcualizador get config => _config;
/// Number of times [guardarActivo] has been called — lets tests assert a
/// persistence write happened exactly once (eq-sync-superficies).
int guardarActivoLlamadas = 0;
@override
Future<ConfiguracionEcualizador> cargar() async => _config;
@override
Future<void> guardarPrincipal(PresetEcualizador preset) async {
_config = ConfiguracionEcualizador(
principal: preset,
porEmisora: _config.porEmisora,
activo: _config.activo,
eqMultiDeviceEnabled: _config.eqMultiDeviceEnabled,
presetsDispositivo: _config.presetsDispositivo,
presetsMatriz: _config.presetsMatriz,
nombresDispositivos: _config.nombresDispositivos,
);
}
@override
Future<void> guardarActivo(bool activo) async {
guardarActivoLlamadas++;
_config = ConfiguracionEcualizador(
principal: _config.principal,
porEmisora: _config.porEmisora,
activo: activo,
eqMultiDeviceEnabled: _config.eqMultiDeviceEnabled,
presetsDispositivo: _config.presetsDispositivo,
presetsMatriz: _config.presetsMatriz,
nombresDispositivos: _config.nombresDispositivos,
);
}
/// Overridden like every other write below: without this, calls fall
/// through to the real `ServicioEcualizador.guardarConfiguracion`, which
/// hits real SharedPreferences and needs a Flutter test binding — a
/// footgun for any test that reaches `EstadoEcualizador.importarConfiguracion`
/// (the backup-import path) through this Fake without also wiring
/// `TestWidgetsFlutterBinding`/mock prefs.
@override
Future<void> guardarConfiguracion(ConfiguracionEcualizador config) async {
_config = config;
}
@override
Future<void> guardarPorEmisora(String uuid, PresetEcualizador preset) async {
final mapa = Map<String, PresetEcualizador>.from(_config.porEmisora);
mapa[uuid] = preset;
_config = ConfiguracionEcualizador(
principal: _config.principal,
porEmisora: mapa,
activo: _config.activo,
eqMultiDeviceEnabled: _config.eqMultiDeviceEnabled,
presetsDispositivo: _config.presetsDispositivo,
presetsMatriz: _config.presetsMatriz,
);
}
@override
Future<void> eliminarPorEmisora(String uuid) async {
final mapa = Map<String, PresetEcualizador>.from(_config.porEmisora);
mapa.remove(uuid);
_config = ConfiguracionEcualizador(
principal: _config.principal,
porEmisora: mapa,
activo: _config.activo,
eqMultiDeviceEnabled: _config.eqMultiDeviceEnabled,
presetsDispositivo: _config.presetsDispositivo,
presetsMatriz: _config.presetsMatriz,
);
}
@override
Future<void> guardarToggleMultiDispositivo(bool habilitado) async {
_config = ConfiguracionEcualizador(
principal: _config.principal,
porEmisora: _config.porEmisora,
activo: _config.activo,
eqMultiDeviceEnabled: habilitado,
presetsDispositivo: _config.presetsDispositivo,
presetsMatriz: _config.presetsMatriz,
);
}
@override
Future<void> guardarPresetDispositivo(
String deviceId,
PresetEcualizador preset,
) async {
final mapa = Map<String, PresetEcualizador>.from(
_config.presetsDispositivo,
);
mapa[deviceId] = preset;
_config = ConfiguracionEcualizador(
principal: _config.principal,
porEmisora: _config.porEmisora,
activo: _config.activo,
eqMultiDeviceEnabled: _config.eqMultiDeviceEnabled,
presetsDispositivo: mapa,
presetsMatriz: _config.presetsMatriz,
);
}
@override
Future<void> guardarPresetMatriz(
String clave,
PresetEcualizador preset,
) async {
final mapa = Map<String, PresetEcualizador>.from(_config.presetsMatriz);
mapa[clave] = preset;
_config = ConfiguracionEcualizador(
principal: _config.principal,
porEmisora: _config.porEmisora,
activo: _config.activo,
eqMultiDeviceEnabled: _config.eqMultiDeviceEnabled,
presetsDispositivo: _config.presetsDispositivo,
presetsMatriz: mapa,
nombresDispositivos: _config.nombresDispositivos,
);
}
@override
Future<void> guardarNombresDispositivos(Map<String, String> nombres) async {
_config = ConfiguracionEcualizador(
principal: _config.principal,
porEmisora: _config.porEmisora,
activo: _config.activo,
eqMultiDeviceEnabled: _config.eqMultiDeviceEnabled,
presetsDispositivo: _config.presetsDispositivo,
presetsMatriz: _config.presetsMatriz,
nombresDispositivos: nombres,
);
}
@override
Future<void> eliminarDispositivo(String deviceId) async {
final presets = Map<String, PresetEcualizador>.from(
_config.presetsDispositivo,
)..remove(deviceId);
final nombres = Map<String, String>.from(_config.nombresDispositivos)
..remove(deviceId);
final matriz = Map<String, PresetEcualizador>.from(_config.presetsMatriz)
..removeWhere((clave, _) {
final separador = clave.indexOf(':');
if (separador == -1) return false;
return clave.substring(separador + 1) == deviceId;
});
_config = ConfiguracionEcualizador(
principal: _config.principal,
porEmisora: _config.porEmisora,
activo: _config.activo,
eqMultiDeviceEnabled: _config.eqMultiDeviceEnabled,
presetsDispositivo: presets,
presetsMatriz: matriz,
nombresDispositivos: nombres,
);
}
}
/// In-memory fake for [ServicioPresetsPersonalizados] (WU13). Avoids any
/// real SharedPreferences I/O in state-layer tests, matching every other
/// `Fake*` service in this file.
class FakeServicioPresetsPersonalizados extends ServicioPresetsPersonalizados {
final List<PresetEcualizador> _presets = [];
@override
Future<List<PresetEcualizador>> listar() async => List.from(_presets);
@override
Future<void> guardar(PresetEcualizador preset) async {
_presets.removeWhere((p) => p.nombre == preset.nombre);
_presets.add(preset);
}
@override
Future<void> eliminar(String nombre) async {
_presets.removeWhere((p) => p.nombre == nombre);
}
}
/// A [ServicioDispositivoAudio] fake that throws on [obtenerDispositivoActual].
///
/// Used to test the graceful-failure path in [EstadoEcualizador.cargarPersistido].
class FakeServicioDispositivoAudioThrows extends ServicioDispositivoAudio {
final _controller = StreamController<DispositivoAudio>.broadcast();
@override
DispositivoAudio? get dispositivoActual => null;
@override
Stream<DispositivoAudio> get onDispositivoCambiado => _controller.stream;
/// Number of times [resubscribir] was called.
int resubscribirLlamadas = 0;
@override
Future<DispositivoAudio> obtenerDispositivoActual() async {
throw Exception('Platform channel error: device unavailable');
}
@override
Future<Map<String, String>> obtenerNombresEmparejados() async {
throw Exception('Platform channel error: bonded devices unavailable');
}
@override
Future<void> resubscribir() async {
resubscribirLlamadas++;
throw Exception('Platform channel error: resubscribe unavailable');
}
@override
Future<bool> solicitarPermisoBluetooth() async => true;
@override
Future<void> dispose() async {
await _controller.close();
}
}
/// Fake implementation of [ServicioDispositivoAudio] for unit tests.
///
/// Use [emitirDispositivo] to push device change events without needing
/// a real platform channel.
class FakeServicioDispositivoAudio extends ServicioDispositivoAudio {
FakeServicioDispositivoAudio({
this.permisoBluetoothConcedido = true,
this.nombresEmparejados = const {},
});
/// MAC → name entries the system would report for PAIRED devices.
final Map<String, String> nombresEmparejados;
@override
Future<Map<String, String>> obtenerNombresEmparejados() async =>
nombresEmparejados;
/// Value returned by [solicitarPermisoBluetooth] (bt-device-identity
/// Phase 2/4 testing strategy — defaults to granted so existing tests
/// that don't care about the permission flow are unaffected).
final bool permisoBluetoothConcedido;
/// Number of times [solicitarPermisoBluetooth] was called.
int solicitarPermisoBluetoothCalls = 0;
/// Number of times [resubscribir] was called.
int resubscribirLlamadas = 0;
final _controller = StreamController<DispositivoAudio>.broadcast();
DispositivoAudio? _dispositivoActual;
@override
DispositivoAudio? get dispositivoActual => _dispositivoActual;
@override
Stream<DispositivoAudio> get onDispositivoCambiado => _controller.stream;
@override
Future<DispositivoAudio> obtenerDispositivoActual() async {
return _dispositivoActual ??
const DispositivoAudio(
id: 'builtin_speaker',
tipo: TipoDispositivo.altavozInterno,
nombre: 'Speaker',
);
}
@override
Future<void> resubscribir() async {
resubscribirLlamadas++;
}
@override
Future<bool> solicitarPermisoBluetooth() async {
solicitarPermisoBluetoothCalls++;
return permisoBluetoothConcedido;
}
/// Helper for tests: push a device change event.
void emitirDispositivo(DispositivoAudio dispositivo) {
_dispositivoActual = dispositivo;
_controller.add(dispositivo);
}
/// Helper for tests: change the cached device WITHOUT emitting a stream
/// event — simulates the stale-subscription bug (activity recreation),
/// where the native device changed but the event never reached Dart and
/// only a fresh query ([obtenerDispositivoActual]) sees the new value.
void establecerDispositivoSinEvento(DispositivoAudio dispositivo) {
_dispositivoActual = dispositivo;
}
@override
Future<void> dispose() async {
await _controller.close();
}
}
/// WU6: promoted from a local class in `pantalla_inicio_test.dart` to this
/// shared helpers file, since `pantalla_buscar_test.dart` now needs the same
/// inert stand-in — any widget test that constructs a full `EstadoRadio`
/// must supply a `servicioGrabacion`, or the default REAL
/// `ServicioGrabacionRadio` touches native platform channels and hangs
/// inside `testWidgets()`.
class FakeServicioGrabacionRadio extends ServicioGrabacionRadio {
final _controller = StreamController<EstadoGrabacionRadio>.broadcast();
@override
EstadoGrabacionRadio get estado => const EstadoGrabacionRadio.inactiva();
@override
Stream<EstadoGrabacionRadio> get estadoStream => _controller.stream;
@override
Future<void> inicializar() async {}
@override
Future<void> dispose() => _controller.close();
}
/// WU14: a recording fake that actually responds to `iniciar`/`detener`
/// in-memory, never touching real files or platform channels (`iniciar` on
/// the real `ServicioGrabacionRadio` opens an HTTP stream to the station's
/// URL and writes to disk — unsafe inside a widget test). Records every
/// call for assertions.
class FakeServicioGrabacionRadioActivable extends ServicioGrabacionRadio {
final _controller = StreamController<EstadoGrabacionRadio>.broadcast();
EstadoGrabacionRadio _estadoActual = const EstadoGrabacionRadio.inactiva();
final List<Duration?> duracionesIniciadas = [];
Emisora? ultimaEmisoraIniciada;
int detenerCalls = 0;
@override
EstadoGrabacionRadio get estado => _estadoActual;
@override
Stream<EstadoGrabacionRadio> get estadoStream => _controller.stream;
@override
Future<void> inicializar() async {}
@override
Future<void> iniciar(
Emisora emisora, {
Duration? duracion,
String? directorio,
}) async {
ultimaEmisoraIniciada = emisora;
duracionesIniciadas.add(duracion);
_estadoActual = EstadoGrabacionRadio(
tipo: EstadoGrabacionRadioTipo.grabando,
emisora: emisora,
inicio: DateTime.now(),
duracionObjetivo: duracion,
);
_controller.add(_estadoActual);
}
@override
Future<void> detener() async {
detenerCalls++;
_estadoActual = const EstadoGrabacionRadio.inactiva();
_controller.add(_estadoActual);
}
@override
Future<void> dispose() => _controller.close();
}
Emisora emisoraDemo({
required String uuid,
required String nombre,
String url = 'https://stream.demo/radio',
}) {
return Emisora(uuid: uuid, nombre: nombre, url: url);
}