diff --git a/lib/widgets/video_controls/video_controls.dart b/lib/widgets/video_controls/video_controls.dart index dd3ef6a4..8398aeff 100644 --- a/lib/widgets/video_controls/video_controls.dart +++ b/lib/widgets/video_controls/video_controls.dart @@ -1154,8 +1154,16 @@ class _PlexVideoControlsState extends State with WindowListen /// Determine if the key event should toggle play/pause based on configured hotkeys. bool _isPlayPauseKey(KeyEvent event) { + final logicalKey = event.logicalKey; final physicalKey = event.physicalKey; + // Always accept hardware media play/pause keys (Android TV remotes) + if (logicalKey == LogicalKeyboardKey.mediaPlayPause || + logicalKey == LogicalKeyboardKey.mediaPlay || + logicalKey == LogicalKeyboardKey.mediaPause) { + return true; + } + // When the shortcuts service is available, respect the configured play/pause hotkey if (_keyboardService != null) { final hotkey = _keyboardService!.hotkeys['play_pause']; @@ -1167,6 +1175,19 @@ class _PlexVideoControlsState extends State with WindowListen return physicalKey == PhysicalKeyboardKey.space || physicalKey == PhysicalKeyboardKey.mediaPlayPause; } + /// Check if a key is a media seek key (Android TV remotes) + bool _isMediaSeekKey(LogicalKeyboardKey key) { + return key == LogicalKeyboardKey.mediaFastForward || + key == LogicalKeyboardKey.mediaRewind || + key == LogicalKeyboardKey.mediaSkipForward || + key == LogicalKeyboardKey.mediaSkipBackward; + } + + /// Check if a key is a media track key (Android TV remotes) + bool _isMediaTrackKey(LogicalKeyboardKey key) { + return key == LogicalKeyboardKey.mediaTrackNext || key == LogicalKeyboardKey.mediaTrackPrevious; + } + bool _isPlayPauseActivation(KeyEvent event) { return event is KeyDownEvent && _isPlayPauseKey(event); } @@ -1304,6 +1325,28 @@ class _PlexVideoControlsState extends State with WindowListen return KeyEventResult.handled; } + // Handle media seek keys (Android TV remotes) + // Uses chapter navigation if chapters are available, otherwise seeks by configured time + if (event is KeyDownEvent && _isMediaSeekKey(key)) { + if (widget.canControl) { + final isForward = + key == LogicalKeyboardKey.mediaFastForward || key == LogicalKeyboardKey.mediaSkipForward; + _seekToChapter(forward: isForward); + } + _showControlsWithFocus(requestFocus: _videoPlayerNavigationEnabled); + return KeyEventResult.handled; + } + + // Handle next/previous track keys (Android TV remotes) + // Uses same behavior as seek keys: chapter navigation or time-based seek + if (event is KeyDownEvent && _isMediaTrackKey(key)) { + if (widget.canControl) { + _seekToChapter(forward: key == LogicalKeyboardKey.mediaTrackNext); + } + _showControlsWithFocus(requestFocus: _videoPlayerNavigationEnabled); + return KeyEventResult.handled; + } + // Handle Back/Escape: show controls if hidden, navigate back if visible if (_isBackKey(key)) { // On Windows/Linux with navigation off, ESC first exits fullscreen