import 'package:audio_service/audio_service.dart'; import 'package:just_audio/just_audio.dart'; import '../modelos/emisora.dart'; /// Estado de reproducción expuesto al UI. enum EstadoReproduccion { detenido, cargando, reproduciendo, pausado, error } /// Wrapper sobre just_audio + audio_service para reproducción de radio en streaming. /// /// ### Uso /// ```dart /// final servicio = ServicioAudio(); /// await servicio.inicializar(); /// await servicio.reproducir(emisora); /// await servicio.pausar(); /// await servicio.detener(); /// ``` /// /// ### Background audio /// Para habilitar reproducción en background, el handler [PluriWaveAudioHandler] /// debe registrarse en main.dart con [AudioService.init]. Si no está registrado, /// just_audio seguirá funcionando en foreground. class ServicioAudio { final AudioPlayer _player = AudioPlayer(); Emisora? _emisoraActual; EstadoReproduccion _estado = EstadoReproduccion.detenido; EstadoReproduccion get estado => _estado; Emisora? get emisoraActual => _emisoraActual; /// Stream de cambios de estado para el UI. Stream get estadoStream => _player.playerStateStream.map( (s) { if (s.processingState == ProcessingState.loading || s.processingState == ProcessingState.buffering) { return EstadoReproduccion.cargando; } if (s.playing) return EstadoReproduccion.reproduciendo; if (s.processingState == ProcessingState.idle) return EstadoReproduccion.detenido; return EstadoReproduccion.pausado; }, ); /// Inicia la reproducción de la [emisora] indicada. Future reproducir(Emisora emisora) async { try { _estado = EstadoReproduccion.cargando; // Si es la misma emisora, reanudar sin recargar if (_emisoraActual?.uuid == emisora.uuid && _player.audioSource != null) { await _player.play(); _estado = EstadoReproduccion.reproduciendo; return; } _emisoraActual = emisora; await _player.stop(); await _player.setUrl(emisora.url); await _player.play(); _estado = EstadoReproduccion.reproduciendo; } on PlayerException catch (_) { _estado = EstadoReproduccion.error; rethrow; } catch (e) { _estado = EstadoReproduccion.error; rethrow; } } /// Pausa la reproducción actual. Future pausar() async { await _player.pause(); _estado = EstadoReproduccion.pausado; } /// Reanuda si estaba pausado. Future reanudar() async { if (_player.audioSource != null) { await _player.play(); _estado = EstadoReproduccion.reproduciendo; } } /// Alterna entre pausa y reproducción. Future togglePlay() async { if (_player.playing) { await pausar(); } else { await reanudar(); } } /// Detiene la reproducción y libera la fuente. Future detener() async { await _player.stop(); _emisoraActual = null; _estado = EstadoReproduccion.detenido; } /// Ajusta el volumen (0.0 - 1.0). Future setVolumen(double volumen) async { await _player.setVolume(volumen.clamp(0.0, 1.0)); } double get volumen => _player.volume; bool get estaSonando => _player.playing; /// Libera recursos. Llamar al destruir la pantalla raíz. Future dispose() async { await _player.dispose(); } } /// Handler de audio_service para reproducción en background con notificación. /// /// Registrar en main.dart: /// ```dart /// final handler = await AudioService.init( /// builder: () => PluriWaveAudioHandler(), /// config: const AudioServiceConfig( /// androidNotificationChannelId: 'es.freetimelab.pluriwave.audio', /// androidNotificationChannelName: 'PluriWave Radio', /// androidNotificationOngoing: true, /// androidStopForegroundOnPause: true, /// ), /// ); /// ``` class PluriWaveAudioHandler extends BaseAudioHandler with SeekHandler { final AudioPlayer _player = AudioPlayer(); PluriWaveAudioHandler() { _player.playerStateStream.listen((state) { final playing = state.playing; final proc = state.processingState; playbackState.add(playbackState.value.copyWith( controls: [ if (playing) MediaControl.pause else MediaControl.play, MediaControl.stop, ], systemActions: const {MediaAction.seek}, androidCompactActionIndices: const [0], processingState: { ProcessingState.idle: AudioProcessingState.idle, ProcessingState.loading: AudioProcessingState.loading, ProcessingState.buffering: AudioProcessingState.buffering, ProcessingState.ready: AudioProcessingState.ready, ProcessingState.completed: AudioProcessingState.completed, }[proc]!, playing: playing, )); }); } @override Future playMediaItem(MediaItem item) async { mediaItem.add(item); await _player.setUrl(item.id); await _player.play(); } @override Future play() => _player.play(); @override Future pause() => _player.pause(); @override Future stop() async { await _player.stop(); await super.stop(); } @override Future seek(Duration position) => _player.seek(position); }