import 'dart:async'; import 'dart:io'; import 'package:flutter/foundation.dart'; import '../../../core/infra/pip/android_pip_controller.dart'; import '../../../shared/utils/app_logger.dart'; const _tag = 'AndroidPipManager'; class AndroidPipManager { AndroidPipManager({required this._onStateChanged}); final VoidCallback _onStateChanged; bool _isInPip = false; bool _isDisposed = false; bool _supported = false; StreamSubscription? _modeSub; StreamSubscription? _actionSub; void Function(PipAction action)? onAction; bool get isInPip => _isInPip; bool get isSupported => _supported; Future initialize() async { if (!Platform.isAndroid) return; final supported = await AndroidPipController.instance.isSupported(); if (_isDisposed) return; if (_supported != supported) { _supported = supported; _onStateChanged(); } if (!_supported) return; if (_modeSub != null || _actionSub != null) return; final isInPip = await AndroidPipController.instance.isInPipMode(); if (_isDisposed) return; if (_isInPip != isInPip) { _isInPip = isInPip; _onStateChanged(); } _modeSub = AndroidPipController.instance.pipModeChanges.listen( (isInPip) { if (_isDisposed) return; if (_isInPip == isInPip) return; _isInPip = isInPip; AppLogger.info(_tag, 'PiP mode changed: $isInPip'); _onStateChanged(); }, onError: (Object e) { AppLogger.warn(_tag, 'PiP mode stream error', e); }, ); _actionSub = AndroidPipController.instance.actionStream.listen( (action) { if (_isDisposed) return; AppLogger.debug(_tag, 'PiP action: $action'); onAction?.call(action); }, onError: (Object e) { AppLogger.warn(_tag, 'PiP action stream error', e); }, ); } Future configure({ int aspectX = 16, int aspectY = 9, bool autoEnterOnLeave = false, bool isPlaying = false, bool hasNext = false, bool hasPrev = false, bool useEpisodeActions = true, }) async { if (!_supported || _isDisposed) return; await AndroidPipController.instance.configure( aspectX: aspectX, aspectY: aspectY, autoEnterOnLeave: autoEnterOnLeave, isPlaying: isPlaying, hasNext: hasNext, hasPrev: hasPrev, useEpisodeActions: useEpisodeActions, ); } Future enter({int? aspectX, int? aspectY}) async { if (!_supported || _isDisposed || _isInPip) return false; return AndroidPipController.instance.enterPip( aspectX: aspectX, aspectY: aspectY, ); } Future updatePlaybackState({ required bool isPlaying, required bool hasNext, required bool hasPrev, required bool useEpisodeActions, }) async { if (!_supported || _isDisposed || !_isInPip) return; await AndroidPipController.instance.updatePlaybackState( isPlaying: isPlaying, hasNext: hasNext, hasPrev: hasPrev, useEpisodeActions: useEpisodeActions, ); } void dispose() { if (_isDisposed) return; _isDisposed = true; final modeSub = _modeSub; _modeSub = null; final actionSub = _actionSub; _actionSub = null; onAction = null; unawaited(() async { await AndroidPipController.instance.configure( autoEnterOnLeave: false, isPlaying: false, ); await modeSub?.cancel(); await actionSub?.cancel(); await AndroidPipController.instance.dispose(); }()); } }