98 lines
3.0 KiB
Dart
98 lines
3.0 KiB
Dart
import 'dart:io';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import '../mpv/mpv.dart';
|
|
import '../services/pip_service.dart';
|
|
import '../utils/app_logger.dart';
|
|
|
|
class VideoPIPManager {
|
|
final Player player;
|
|
Size? _playerSize;
|
|
|
|
VideoPIPManager({required this.player, Size? initialPlayerSize}) : _playerSize = initialPlayerSize;
|
|
|
|
Size? get playerSize => _playerSize;
|
|
|
|
/// Callback to prepare video filter before entering PiP
|
|
VoidCallback? onBeforeEnterPip;
|
|
|
|
/// Update player size for PiP aspect ratio calculation
|
|
void updatePlayerSize(Size size) {
|
|
_playerSize = size;
|
|
}
|
|
|
|
ValueNotifier<bool> get isPipActive => PipService().isPipActive;
|
|
|
|
/// Get current video dimensions (display or storage or fallback to viewport)
|
|
Future<(int? width, int? height)> _getVideoDimensions() async {
|
|
int? width;
|
|
int? height;
|
|
|
|
try {
|
|
final dwidth = await player.getProperty('dwidth');
|
|
final dheight = await player.getProperty('dheight');
|
|
if (dwidth != null && dheight != null) {
|
|
width = int.tryParse(dwidth);
|
|
height = int.tryParse(dheight);
|
|
}
|
|
} catch (e) {
|
|
appLogger.d('VideoPipManager: dwidth/dheight unavailable', error: e);
|
|
}
|
|
|
|
if (width == null || height == null) {
|
|
try {
|
|
final videoWidth = await player.getProperty('width');
|
|
final videoHeight = await player.getProperty('height');
|
|
if (videoWidth != null && videoHeight != null) {
|
|
width = int.tryParse(videoWidth);
|
|
height = int.tryParse(videoHeight);
|
|
}
|
|
} catch (e) {
|
|
appLogger.d('VideoPipManager: width/height unavailable', error: e);
|
|
}
|
|
}
|
|
|
|
width ??= _playerSize?.width.toInt();
|
|
height ??= _playerSize?.height.toInt();
|
|
|
|
return (width, height);
|
|
}
|
|
|
|
Future<(bool success, String? error)> togglePIP() async {
|
|
final supported = await PipService.isSupported();
|
|
if (!supported) return (false, 'PiP not supported on this device');
|
|
|
|
// If PiP is already active, exit it
|
|
if (isPipActive.value) {
|
|
await PipService.exit();
|
|
return (true, null);
|
|
}
|
|
|
|
// Reset video filter to contain mode before entering PiP. Android, iOS,
|
|
// and macOS all reuse the inline video surface/layer for PiP.
|
|
if (Platform.isAndroid || Platform.isIOS || Platform.isMacOS) {
|
|
onBeforeEnterPip?.call();
|
|
// Wait a frame for the filter change to take effect
|
|
await Future.delayed(const Duration(milliseconds: 50));
|
|
}
|
|
|
|
final dims = await _getVideoDimensions();
|
|
return await PipService.enter(width: dims.$1, height: dims.$2);
|
|
}
|
|
|
|
Future<void> updateAutoPipState({required bool isPlaying}) async {
|
|
if (!isPlaying) {
|
|
await PipService.setAutoPipReady(ready: false);
|
|
return;
|
|
}
|
|
|
|
final dims = await _getVideoDimensions();
|
|
await PipService.setAutoPipReady(ready: true, width: dims.$1, height: dims.$2);
|
|
}
|
|
|
|
/// Disable auto-PiP (called on dispose or when leaving player)
|
|
Future<void> disableAutoPip() async {
|
|
await PipService.setAutoPipReady(ready: false);
|
|
}
|
|
}
|