@@ -46,6 +46,7 @@ import '../utils/video_player_navigation.dart';
|
||||
import '../widgets/video_controls/video_controls.dart';
|
||||
import '../focus/focusable_wrapper.dart';
|
||||
import '../focus/input_mode_tracker.dart';
|
||||
import '../focus/dpad_navigator.dart';
|
||||
import '../focus/key_event_utils.dart';
|
||||
import '../i18n/strings.g.dart';
|
||||
import '../watch_together/providers/watch_together_provider.dart';
|
||||
@@ -1726,7 +1727,26 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen> with WidgetsBindin
|
||||
focusNode: _screenFocusNode,
|
||||
autofocus: isCurrentRoute,
|
||||
canRequestFocus: isCurrentRoute,
|
||||
onKeyEvent: (node, event) => isCurrentRoute ? KeyEventResult.handled : KeyEventResult.ignored,
|
||||
onKeyEvent: (node, event) {
|
||||
if (!isCurrentRoute) return KeyEventResult.ignored;
|
||||
// Safety net: if this screen-level node itself has primary focus
|
||||
// (no descendant focused, e.g. after controls auto-hide), self-heal.
|
||||
if (node.hasPrimaryFocus) {
|
||||
// Handle BACK immediately so the user is never stuck.
|
||||
final backResult = handleBackKeyAction(event, _handleBackButton);
|
||||
if (backResult != KeyEventResult.ignored) return backResult;
|
||||
// Redirect focus to the first traversable descendant (video controls)
|
||||
// and show controls immediately so the first key press isn't swallowed.
|
||||
if (event.isActionable) {
|
||||
_controlsVisible.value = true;
|
||||
final descendants = node.traversalDescendants;
|
||||
if (descendants.isNotEmpty) {
|
||||
descendants.first.requestFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
return KeyEventResult.handled;
|
||||
},
|
||||
child: _isPlayerInitialized && player != null ? _buildVideoPlayer(context) : _buildLoadingSpinner(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -260,6 +260,8 @@ class _PlexVideoControlsState extends State<PlexVideoControls> with WindowListen
|
||||
HardwareKeyboard.instance.addHandler(_handleGlobalKeyEvent);
|
||||
// Listen for first frame to start auto-hide timer
|
||||
widget.hasFirstFrame?.addListener(_onFirstFrameReady);
|
||||
// Listen for external requests to show controls (e.g. screen-level focus recovery)
|
||||
widget.controlsVisible?.addListener(_onControlsVisibleExternal);
|
||||
}
|
||||
|
||||
/// Called when hasFirstFrame changes - start auto-hide timer when first frame is ready
|
||||
@@ -269,6 +271,14 @@ class _PlexVideoControlsState extends State<PlexVideoControls> with WindowListen
|
||||
}
|
||||
}
|
||||
|
||||
/// Called when controlsVisible is set externally (e.g. screen-level focus recovery
|
||||
/// after controls auto-hide ejects focus on Android TV).
|
||||
void _onControlsVisibleExternal() {
|
||||
if (widget.controlsVisible?.value == true && !_showControls && mounted) {
|
||||
_showControlsWithFocus();
|
||||
}
|
||||
}
|
||||
|
||||
/// Focus play/pause button if we're in keyboard navigation mode (desktop/TV only)
|
||||
void _focusPlayPauseIfKeyboardMode() {
|
||||
if (!mounted) return;
|
||||
@@ -515,6 +525,7 @@ class _PlexVideoControlsState extends State<PlexVideoControls> with WindowListen
|
||||
@override
|
||||
void dispose() {
|
||||
HardwareKeyboard.instance.removeHandler(_handleGlobalKeyEvent);
|
||||
widget.controlsVisible?.removeListener(_onControlsVisibleExternal);
|
||||
widget.hasFirstFrame?.removeListener(_onFirstFrameReady);
|
||||
_hideTimer?.cancel();
|
||||
_feedbackTimer?.cancel();
|
||||
|
||||
Reference in New Issue
Block a user