fix: ESC exits fullscreen without closing video player

close #533
This commit is contained in:
edde746
2026-02-25 07:17:24 +01:00
parent 50660013cc
commit 45e792e3ab
2 changed files with 46 additions and 8 deletions
+11 -1
View File
@@ -171,6 +171,9 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen> with WidgetsBindin
// key events never escape the video player route.
late final FocusNode _screenFocusNode;
// Cached setting: when false on Windows/Linux, ESC should not exit the player
bool _videoPlayerNavigationEnabled = false;
// App lifecycle state tracking
bool _wasPlayingBeforeInactive = false;
@@ -393,6 +396,7 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen> with WidgetsBindin
try {
// Load buffer size from settings
final settingsService = await SettingsService.getInstance();
_videoPlayerNavigationEnabled = settingsService.getVideoPlayerNavigationEnabled();
final bufferSizeMB = settingsService.getBufferSize();
final enableHardwareDecoding = settingsService.getEnableHardwareDecoding();
final debugLoggingEnabled = settingsService.getEnableDebugLogging();
@@ -2327,7 +2331,13 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen> with WidgetsBindin
canRequestFocus: isCurrentRoute,
onKeyEvent: (node, event) {
if (!isCurrentRoute) return KeyEventResult.ignored;
// Back keys always pass through — handled by PopScope (system back
// On Windows/Linux with navigation off, consume ESC so Flutter's
// DismissAction doesn't trigger a route pop. The video controls'
// global key handler manages fullscreen/controls toggle instead.
if (!_videoPlayerNavigationEnabled && (Platform.isWindows || Platform.isLinux) && event.logicalKey.isBackKey) {
return KeyEventResult.handled;
}
// Back keys pass through — handled by PopScope (system back
// gesture) or overlay sheet's onKeyEvent.
if (event.logicalKey.isBackKey) return KeyEventResult.ignored;
// Self-heal: if this node itself has primary focus (no descendant
+35 -7
View File
@@ -14,6 +14,7 @@ import 'package:flutter/services.dart'
PhysicalKeyboardKey,
KeyEvent,
KeyDownEvent,
KeyUpEvent,
HardwareKeyboard;
import '../../services/fullscreen_state_manager.dart';
import '../../services/macos_window_service.dart';
@@ -1288,6 +1289,14 @@ class _PlexVideoControlsState extends State<PlexVideoControls> with WindowListen
}
}
/// Exit fullscreen if the window is actually fullscreen (async check).
/// Used by ESC handler on Windows/Linux to avoid relying on _isFullscreen flag.
Future<void> _exitFullscreenIfNeeded() async {
if (await windowManager.isFullScreen()) {
await FullscreenStateManager().exitFullscreen();
}
}
/// Initialize always-on-top state from window manager (desktop only)
Future<void> _initAlwaysOnTopState() async {
final isOnTop = await windowManager.isAlwaysOnTop();
@@ -1370,8 +1379,9 @@ class _PlexVideoControlsState extends State<PlexVideoControls> with WindowListen
bool _handleGlobalKeyEvent(KeyEvent event) {
if (!mounted) return false;
// TV back key fallback — Focus.onKeyEvent won't fire if _focusNode lost focus
if (PlatformDetector.isTV() && event.logicalKey.isBackKey) {
// Back key fallback when _focusNode lost focus (TV, or desktop with nav on).
// Focus.onKeyEvent won't fire if _focusNode lost focus, so handle ESC here.
if ((_videoPlayerNavigationEnabled || PlatformDetector.isTV()) && event.logicalKey.isBackKey) {
if (!_focusNode.hasFocus) {
// Skip if an overlay sheet is open — the sheet's FocusScope handles
// back keys via its own onKeyEvent. Without this check, this global
@@ -1407,6 +1417,20 @@ class _PlexVideoControlsState extends State<PlexVideoControls> with WindowListen
// (e.g. after controls auto-hide). The !hasFocus guard prevents
// double-handling when the Focus onKeyEvent already processes the event.
if (!_focusNode.hasFocus && _keyboardService != null) {
// On Windows/Linux with navigation off, ESC only exits fullscreen —
// never exits the player. Intercept before the keyboard shortcuts
// service which would call onBack and pop the route.
// Skip if an overlay sheet is open — let the sheet handle ESC.
if (!_videoPlayerNavigationEnabled && (Platform.isWindows || Platform.isLinux) && event.logicalKey.isBackKey) {
final sheetOpen = OverlaySheetController.maybeOf(context)?.isOpen ?? false;
if (!sheetOpen) {
if (event is KeyUpEvent) {
_exitFullscreenIfNeeded();
}
_focusNode.requestFocus();
return true;
}
}
final result = _keyboardService!.handleVideoPlayerKeyEvent(
event,
widget.player,
@@ -1521,12 +1545,16 @@ class _PlexVideoControlsState extends State<PlexVideoControls> with WindowListen
focusNode: _focusNode,
autofocus: true,
onKeyEvent: (node, event) {
final backResult = handleBackKeyAction(event, () {
// On Windows/Linux with navigation off, ESC first exits fullscreen
if (!_videoPlayerNavigationEnabled && _isFullscreen && (Platform.isWindows || Platform.isLinux)) {
_toggleFullscreen();
return;
// On Windows/Linux with navigation off, ESC only exits fullscreen —
// never exits the player. Consume all back key events and check
// actual window state asynchronously.
if (!_videoPlayerNavigationEnabled && (Platform.isWindows || Platform.isLinux) && event.logicalKey.isBackKey) {
if (event is KeyUpEvent) {
_exitFullscreenIfNeeded();
}
return KeyEventResult.handled;
}
final backResult = handleBackKeyAction(event, () {
if (!_showControls) {
_showControlsWithFocus();
return;