diff --git a/lib/services/keyboard_shortcuts_service.dart b/lib/services/keyboard_shortcuts_service.dart index 412c28dc..ba33ece0 100644 --- a/lib/services/keyboard_shortcuts_service.dart +++ b/lib/services/keyboard_shortcuts_service.dart @@ -224,6 +224,21 @@ class KeyboardShortcutsService { return KeyEventResult.ignored; } + /// Seeks by the given offset (can be positive or negative) while clamping + /// the result between 0 and the video duration + void _seekWithClamping(Player player, Duration offset) { + final currentPosition = player.state.position; + final duration = player.state.duration; + final newPosition = currentPosition + offset; + + // Clamp between 0 and video duration + final clampedPosition = newPosition.isNegative + ? Duration.zero + : (newPosition > duration ? duration : newPosition); + + player.seek(clampedPosition); + } + void _executeAction( String action, Player player, @@ -247,20 +262,16 @@ class KeyboardShortcutsService { player.setVolume(newVolume); break; case 'seek_forward': - final newPosition = player.state.position + const Duration(seconds: 10); - player.seek(newPosition); + _seekWithClamping(player, const Duration(seconds: 10)); break; case 'seek_backward': - final newPosition = player.state.position - const Duration(seconds: 10); - player.seek(newPosition.isNegative ? Duration.zero : newPosition); + _seekWithClamping(player, const Duration(seconds: -10)); break; case 'seek_forward_large': - final newPosition = player.state.position + const Duration(seconds: 30); - player.seek(newPosition); + _seekWithClamping(player, const Duration(seconds: 30)); break; case 'seek_backward_large': - final newPosition = player.state.position - const Duration(seconds: 30); - player.seek(newPosition.isNegative ? Duration.zero : newPosition); + _seekWithClamping(player, const Duration(seconds: -30)); break; case 'fullscreen_toggle': onToggleFullscreen?.call(); diff --git a/lib/widgets/plex_video_controls.dart b/lib/widgets/plex_video_controls.dart index b2c71102..8a5c58d9 100644 --- a/lib/widgets/plex_video_controls.dart +++ b/lib/widgets/plex_video_controls.dart @@ -266,8 +266,7 @@ class _PlexVideoControlsState extends State void _seekToPreviousChapter() { if (_chapters.isEmpty) { // No chapters - seek backward 10 seconds - final currentPosition = widget.player.state.position; - widget.player.seek(currentPosition - const Duration(seconds: 10)); + _seekWithClamping(const Duration(seconds: -10)); return; } @@ -290,8 +289,7 @@ class _PlexVideoControlsState extends State void _seekToNextChapter() { if (_chapters.isEmpty) { // No chapters - seek forward 10 seconds - final currentPosition = widget.player.state.position; - widget.player.seek(currentPosition + const Duration(seconds: 10)); + _seekWithClamping(const Duration(seconds: 10)); return; } @@ -307,6 +305,21 @@ class _PlexVideoControlsState extends State } } + /// Seeks by the given offset (can be positive or negative) while clamping + /// the result between 0 and the video duration + void _seekWithClamping(Duration offset) { + final currentPosition = widget.player.state.position; + final duration = widget.player.state.duration; + final newPosition = currentPosition + offset; + + // Clamp between 0 and video duration + final clampedPosition = newPosition.isNegative + ? Duration.zero + : (newPosition > duration ? duration : newPosition); + + widget.player.seek(clampedPosition); + } + Future _toggleFullscreen() async { if (!PlatformDetector.isMobile(context)) { // Query actual window state to determine what action to take @@ -554,10 +567,7 @@ class _PlexVideoControlsState extends State ), iconSize: 48, onPressed: () { - final currentPosition = widget.player.state.position; - widget.player.seek( - currentPosition - const Duration(seconds: 10), - ); + _seekWithClamping(const Duration(seconds: -10)); }, ), ), @@ -599,10 +609,7 @@ class _PlexVideoControlsState extends State ), iconSize: 48, onPressed: () { - final currentPosition = widget.player.state.position; - widget.player.seek( - currentPosition + const Duration(seconds: 10), - ); + _seekWithClamping(const Duration(seconds: 10)); }, ), ),