Files
sm-emby-share/lib/features/player/services/android_pip_manager.dart
T

139 lines
3.5 KiB
Dart
Raw Normal View History

2026-07-14 11:11:36 +08:00
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<bool>? _modeSub;
StreamSubscription<PipAction>? _actionSub;
void Function(PipAction action)? onAction;
bool get isInPip => _isInPip;
bool get isSupported => _supported;
Future<void> 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<void> 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<bool> enter({int? aspectX, int? aspectY}) async {
if (!_supported || _isDisposed || _isInPip) return false;
return AndroidPipController.instance.enterPip(
aspectX: aspectX,
aspectY: aspectY,
);
}
Future<void> 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();
}());
}
}