feat(v0.3.0): ecualizador + favoritos en tarjeta + emisoras custom + export/import + fix MainActivity
Some checks failed
Flutter CI/CD — PluriWave / Test + Build (push) Has been cancelled
Some checks failed
Flutter CI/CD — PluriWave / Test + Build (push) Has been cancelled
- MainActivity: extiende AudioServiceActivity (fix pantalla en blanco) - ServicioAudio: AndroidEqualizer en AudioPipeline, aplicarPreset(), setBanda() - PresetEcualizador: modelo independiente (Flat/Rock/Pop/BassBoost/Jazz/Voz) - EcualizadorWidget: 5 sliders verticales + PresetsEcualizadorWidget - TarjetaEmisora: botón favorito visible en grid y lista (toggle con SnackBar) - EstadoRadio: emisoras custom (CRUD), export/import JSON v1, presets por emisora - PantallaAjustes: ecualizador interactivo, form añadir emisora, backup export/import - pubspec: +file_picker ^8.1.7, +uuid ^4.5.1
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import 'package:audio_service/audio_service.dart';
|
||||
import 'package:just_audio/just_audio.dart';
|
||||
import '../modelos/emisora.dart';
|
||||
import '../modelos/preset_ecualizador.dart';
|
||||
|
||||
/// Estado de reproducción expuesto al UI.
|
||||
enum EstadoReproduccion { detenido, cargando, reproduciendo, pausado, error }
|
||||
@@ -10,20 +11,14 @@ enum EstadoReproduccion { detenido, cargando, reproduciendo, pausado, error }
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
PluriWaveAudioHandler? _handlerGlobal;
|
||||
|
||||
/// Registra el handler. Llamar desde main.dart tras AudioService.init.
|
||||
void registrarHandler(PluriWaveAudioHandler handler) {
|
||||
_handlerGlobal = handler;
|
||||
}
|
||||
|
||||
/// Wrapper de alto nivel para el UI.
|
||||
///
|
||||
/// Delega TODA la reproducción al [PluriWaveAudioHandler] para garantizar
|
||||
/// que el audio siga vivo en background con notificación foreground.
|
||||
class ServicioAudio {
|
||||
PluriWaveAudioHandler get _handler {
|
||||
assert(_handlerGlobal != null,
|
||||
'ServicioAudio: handler no registrado. '
|
||||
'Llama registrarHandler() en main.dart tras AudioService.init.');
|
||||
assert(_handlerGlobal != null, 'registrarHandler() no fue llamado en main.dart');
|
||||
return _handlerGlobal!;
|
||||
}
|
||||
|
||||
@@ -68,55 +63,52 @@ class ServicioAudio {
|
||||
}
|
||||
|
||||
Future<void> detener() => _handler.stop();
|
||||
|
||||
Future<void> setVolumen(double vol) => _handler.setVolume(vol.clamp(0.0, 1.0));
|
||||
|
||||
Future<void> setVolumen(double vol) => _handler.setVolumen(vol);
|
||||
double get volumen => _handler.volumen;
|
||||
bool get estaSonando => _handler.playbackState.value.playing;
|
||||
|
||||
/// No-op: el handler se limpia en main.dart al cerrar la app.
|
||||
Future<void> dispose() async {}
|
||||
|
||||
// ── Ecualizador ──────────────────────────────────────────────────────────
|
||||
AndroidEqualizer? get ecualizador => _handler.ecualizador;
|
||||
bool get ecualizadorDisponible => _handler.ecualizadorDisponible;
|
||||
PresetEcualizador get presetActual => _handler.presetActual;
|
||||
|
||||
Future<void> aplicarPreset(PresetEcualizador preset) =>
|
||||
_handler.aplicarPreset(preset);
|
||||
|
||||
Future<void> setBanda(int index, double db) =>
|
||||
_handler.setBanda(index, db);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// AudioHandler — núcleo del audio en background
|
||||
// AudioHandler
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
/// Handler de audio_service.
|
||||
///
|
||||
/// Gestiona la reproducción con `just_audio` y mantiene la notificación
|
||||
/// foreground activa mientras hay audio reproduciéndose.
|
||||
///
|
||||
/// ### Inicialización 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,
|
||||
/// androidNotificationIcon: 'drawable/ic_stat_radio',
|
||||
/// ),
|
||||
/// );
|
||||
/// registrarHandler(handler);
|
||||
/// ```
|
||||
class PluriWaveAudioHandler extends BaseAudioHandler with SeekHandler {
|
||||
final AudioPlayer _player = AudioPlayer();
|
||||
final AndroidEqualizer _eq = AndroidEqualizer();
|
||||
|
||||
late final AudioPlayer _player = AudioPlayer(
|
||||
audioPipeline: AudioPipeline(androidAudioEffects: [_eq]),
|
||||
);
|
||||
|
||||
Emisora? emisoraActual;
|
||||
double _volumen = 1.0;
|
||||
double get volumen => _volumen;
|
||||
|
||||
AndroidEqualizer? get ecualizador => _eq;
|
||||
bool _eqDisponible = false;
|
||||
bool get ecualizadorDisponible => _eqDisponible;
|
||||
|
||||
PresetEcualizador _presetActual = PresetEcualizador.flat;
|
||||
PresetEcualizador get presetActual => _presetActual;
|
||||
|
||||
PluriWaveAudioHandler() {
|
||||
_setupStreams();
|
||||
}
|
||||
|
||||
void _setupStreams() {
|
||||
// Propagar estado del player → playbackState (lo que ve la notificación)
|
||||
_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,
|
||||
@@ -131,7 +123,6 @@ class PluriWaveAudioHandler extends BaseAudioHandler with SeekHandler {
|
||||
));
|
||||
});
|
||||
|
||||
// Actualizar bufferedPosition
|
||||
_player.bufferedPositionStream.listen((pos) {
|
||||
playbackState.add(playbackState.value.copyWith(bufferedPosition: pos));
|
||||
});
|
||||
@@ -154,6 +145,8 @@ class PluriWaveAudioHandler extends BaseAudioHandler with SeekHandler {
|
||||
await _player.stop();
|
||||
await _player.setUrl(item.id);
|
||||
await _player.play();
|
||||
// Habilitar ecualizador tras reproducir (necesita audio activo)
|
||||
await _activarEcualizador();
|
||||
} on PlayerException catch (e) {
|
||||
playbackState.add(playbackState.value.copyWith(
|
||||
processingState: AudioProcessingState.error,
|
||||
@@ -164,6 +157,52 @@ class PluriWaveAudioHandler extends BaseAudioHandler with SeekHandler {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _activarEcualizador() async {
|
||||
try {
|
||||
final params = await _eq.parameters;
|
||||
_eqDisponible = params.bands.isNotEmpty;
|
||||
if (_eqDisponible) {
|
||||
await _eq.setEnabled(true);
|
||||
await aplicarPreset(_presetActual);
|
||||
}
|
||||
} catch (_) {
|
||||
_eqDisponible = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Aplica un preset al ecualizador nativo Android.
|
||||
Future<void> aplicarPreset(PresetEcualizador preset) async {
|
||||
_presetActual = preset;
|
||||
if (!_eqDisponible) return;
|
||||
try {
|
||||
final params = await _eq.parameters;
|
||||
for (int i = 0; i < params.bands.length && i < preset.bandas.length; i++) {
|
||||
await params.bands[i].setGain(preset.bandas[i]);
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
/// Ajusta una banda individual.
|
||||
Future<void> setBanda(int index, double db) async {
|
||||
if (!_eqDisponible) return;
|
||||
final bandas = List<double>.from(_presetActual.bandas);
|
||||
if (index >= 0 && index < bandas.length) {
|
||||
bandas[index] = db;
|
||||
_presetActual = _presetActual.copyWithBandas(bandas);
|
||||
}
|
||||
try {
|
||||
final params = await _eq.parameters;
|
||||
if (index < params.bands.length) {
|
||||
await params.bands[index].setGain(db);
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
Future<void> setVolumen(double vol) async {
|
||||
_volumen = vol.clamp(0.0, 1.0);
|
||||
await _player.setVolume(_volumen);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> play() => _player.play();
|
||||
|
||||
@@ -181,11 +220,6 @@ class PluriWaveAudioHandler extends BaseAudioHandler with SeekHandler {
|
||||
@override
|
||||
Future<void> seek(Duration position) => _player.seek(position);
|
||||
|
||||
Future<void> setVolume(double vol) async {
|
||||
_volumen = vol.clamp(0.0, 1.0);
|
||||
await _player.setVolume(_volumen);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onTaskRemoved() async {
|
||||
await stop();
|
||||
|
||||
Reference in New Issue
Block a user