Files
plezy/lib/screens/video_player/parts/pip.dart
T
edde746 352b88109b refactor: extract shared mixins and helpers, drop dead abstractions
Introduces shared seams for paginated views, D-pad reorder, media control
routing, async singletons and the device method channel, then points the
open-coded copies at them.

Also removes unused models and duplicated provider/server plumbing, folds
the twice-implemented artifact store in the server, and factors the
repeated Flutter toolchain prologue in CI into a composite action.
2026-07-26 06:09:48 +02:00

111 lines
3.6 KiB
Dart

part of '../../video_player_screen.dart';
extension _VideoPlayerPipMethods on VideoPlayerScreenState {
void _attachPipStateListener() {
final pipState = PipService().isPipActive;
pipState.removeListener(_onPipStateChanged);
pipState.addListener(_onPipStateChanged);
}
void _detachPipStateListener() {
PipService().isPipActive.removeListener(_onPipStateChanged);
}
void _clearAutoPipEnteringCallback() {
final callback = _autoPipEnteringCallback;
if (callback != null && identical(PipService.onAutoPipEntering, callback)) {
PipService.onAutoPipEntering = null;
}
_autoPipEnteringCallback = null;
}
/// Initialize VideoFilterManager and VideoPIPManager if not already set up.
/// Called from both live TV and VOD playback paths.
Future<void> _initVideoFilterAndPip() async {
final currentPlayer = player;
if (!mounted || currentPlayer == null) return;
if (_videoFilterManager != null && _videoPIPManager != null) {
_attachPipStateListener();
return;
}
final needsVideoFilter = _videoFilterManager == null;
final settings = needsVideoFilter ? await SettingsService.getInstance() : null;
if (!mounted || player != currentPlayer) return;
final initialPlayerSize = _lastVideoLayoutPlayer == currentPlayer ? _lastVideoLayoutSize : null;
if (needsVideoFilter && _videoFilterManager == null && settings != null) {
_videoFilterManager = VideoFilterManager(
player: currentPlayer,
initialBoxFitMode: settings.read(SettingsService.defaultBoxFitMode),
initialPlayerSize: initialPlayerSize,
onBoxFitModeChanged: (mode) => settings.write(SettingsService.defaultBoxFitMode, mode),
);
unawaited(_videoFilterManager!.updateVideoFilter());
}
_videoPIPManager ??= VideoPIPManager(
player: currentPlayer,
playerSize: () => _lastVideoLayoutPlayer == currentPlayer ? _lastVideoLayoutSize : null,
);
_videoPIPManager!.onBeforeEnterPip = _preparePipFiltersForEntry;
_attachPipStateListener();
}
Future<void> _togglePIPMode() async {
final result = await _videoPIPManager?.togglePIP();
if (result != null && !result.$1 && mounted) {
_restorePipFiltersAfterExit();
showErrorSnackBar(context, result.$2 ?? t.videoControls.pipFailed);
}
}
void _preparePipFiltersForEntry() {
if (!mounted) return;
if (_pipFiltersPrepared) return;
_pipFiltersPrepared = true;
_videoFilterManager?.enterPipMode();
}
void _restorePipFiltersAfterExit() {
if (!mounted) {
_pipFiltersPrepared = false;
return;
}
final filterManager = _videoFilterManager;
if (filterManager == null) {
_pipFiltersPrepared = false;
return;
}
final restoreAmbient = filterManager.hadAmbientLightingBeforePip;
filterManager.exitPipMode();
if (restoreAmbient) {
filterManager.clearPipAmbientLightingFlag();
unawaited(_restoreAmbientLighting());
}
_pipFiltersPrepared = false;
}
/// Handle PiP state changes to restore video scaling when exiting PiP
void _onPipStateChanged() {
if (!mounted || player == null) {
_detachPipStateListener();
return;
}
final isInPip = PipService().isPipActive.value;
_setAndroidAutoPipTransitionInFlight(false, reason: 'pip_state_changed');
_recordLifecycleState('pip_state_changed', action: isInPip ? 'entered' : 'exited');
if (_videoPIPManager == null || _videoFilterManager == null) return;
if (isInPip) {
_preparePipFiltersForEntry();
} else {
_restorePipFiltersAfterExit();
}
}
}