import 'dart:async'; import 'package:flutter/services.dart'; import '../modelos/dispositivo_audio.dart'; /// Abstract service for audio device detection. /// /// Implementations: /// - [ServicioDispositivoAudioReal] — platform channel (Android/iOS) /// - `FakeServicioDispositivoAudio` (test/helpers/fakes.dart) — for unit tests abstract class ServicioDispositivoAudio { /// The last known active audio output device, or null if none detected yet. DispositivoAudio? get dispositivoActual; /// A broadcast stream that emits whenever the active audio output device /// changes (connect or disconnect event from the platform). Stream get onDispositivoCambiado; /// Requests the current active device from the platform synchronously /// (method channel round-trip). Returns the cached value if already known. Future obtenerDispositivoActual(); /// Cancels the device-change subscription and releases resources. Future dispose(); } /// Platform channel implementation of [ServicioDispositivoAudio]. /// /// Talks to `pluriwave/audio_devices` on Android (Kotlin) and iOS (Swift). /// Type int constants (from platform channel protocol): /// 2 → altavozInterno (builtin_speaker) /// 3 → auricularesCable (wired_headset) /// 8 → bluetoothA2dp (`bt_a2dp:`) /// 14 → usbAudio (`usb_headset:
`) class ServicioDispositivoAudioReal extends ServicioDispositivoAudio { static const _channelName = 'pluriwave/audio_devices'; final MethodChannel _methodChannel; final EventChannel _eventChannel; final _controller = StreamController.broadcast(); StreamSubscription? _eventSub; DispositivoAudio? _dispositivoActual; ServicioDispositivoAudioReal({ MethodChannel? methodChannel, EventChannel? eventChannel, }) : _methodChannel = methodChannel ?? const MethodChannel(_channelName), _eventChannel = eventChannel ?? const EventChannel(_channelName) { _subscribeToEvents(); } void _subscribeToEvents() { _eventSub = _eventChannel.receiveBroadcastStream().listen( (dynamic event) { if (event is Map) { final device = _mapToDispositivo(Map.from(event)); _dispositivoActual = device; _controller.add(device); } }, onError: (_) { /* swallow platform errors; stream continues */ }, cancelOnError: false, ); } @override DispositivoAudio? get dispositivoActual => _dispositivoActual; @override Stream get onDispositivoCambiado => _controller.stream; @override Future obtenerDispositivoActual() async { final raw = await _methodChannel.invokeMethod('getActiveDevice'); final map = Map.from(raw ?? {}); final device = _mapToDispositivo(map); _dispositivoActual = device; return device; } @override Future dispose() async { await _eventSub?.cancel(); await _controller.close(); } static DispositivoAudio _mapToDispositivo(Map map) { final id = map['id'] as String? ?? 'builtin_speaker'; final type = map['type'] as int? ?? 2; final name = map['name'] as String? ?? ''; return DispositivoAudio(id: id, tipo: _tipoDesdeInt(type), nombre: name); } static TipoDispositivo _tipoDesdeInt(int type) { switch (type) { case 2: return TipoDispositivo.altavozInterno; case 3: return TipoDispositivo.auricularesCable; case 8: return TipoDispositivo.bluetoothA2dp; case 14: return TipoDispositivo.usbAudio; default: return TipoDispositivo.desconocido; } } }