diff --git a/lib/screens/video_player_screen.dart b/lib/screens/video_player_screen.dart index 1acb64ab..ac145f25 100644 --- a/lib/screens/video_player_screen.dart +++ b/lib/screens/video_player_screen.dart @@ -1877,19 +1877,23 @@ class VideoPlayerScreenState extends State with WidgetsBindin receiver.onSeekForward = () async { if (player == null) return; final settings = await SettingsService.getInstance(); - final target = clampSeekPosition( - player!, - player!.state.position + Duration(seconds: settings.getSeekTimeSmall()), - ); + final seekSeconds = settings.getSeekTimeSmall(); + if (widget.isLive && _captureBuffer != null) { + await _seekLivePosition(_currentPositionEpoch + seekSeconds); + return; + } + final target = clampSeekPosition(player!, player!.state.position + Duration(seconds: seekSeconds)); await player!.seek(target); }; receiver.onSeekBackward = () async { if (player == null) return; final settings = await SettingsService.getInstance(); - final target = clampSeekPosition( - player!, - player!.state.position - Duration(seconds: settings.getSeekTimeSmall()), - ); + final seekSeconds = settings.getSeekTimeSmall(); + if (widget.isLive && _captureBuffer != null) { + await _seekLivePosition(_currentPositionEpoch - seekSeconds); + return; + } + final target = clampSeekPosition(player!, player!.state.position - Duration(seconds: seekSeconds)); await player!.seek(target); }; receiver.onVolumeUp = () async { diff --git a/lib/services/keyboard_shortcuts_service.dart b/lib/services/keyboard_shortcuts_service.dart index 76fd96e4..5338da83 100644 --- a/lib/services/keyboard_shortcuts_service.dart +++ b/lib/services/keyboard_shortcuts_service.dart @@ -139,6 +139,8 @@ class KeyboardShortcutsService { VoidCallback? onBack, VoidCallback? onToggleShader, VoidCallback? onSkipMarker, + int? currentPositionEpoch, + ValueChanged? onLiveSeek, }) { if (event is! KeyDownEvent) return KeyEventResult.ignored; @@ -216,6 +218,8 @@ class KeyboardShortcutsService { onPreviousChapter, onToggleShader: onToggleShader, onSkipMarker: onSkipMarker, + currentPositionEpoch: currentPositionEpoch, + onLiveSeek: onLiveSeek, ); return KeyEventResult.handled; } @@ -235,7 +239,18 @@ class KeyboardShortcutsService { VoidCallback? onPreviousChapter, { VoidCallback? onToggleShader, VoidCallback? onSkipMarker, + int? currentPositionEpoch, + ValueChanged? onLiveSeek, }) { + void performSeek(int offsetSeconds) { + if (onLiveSeek != null && currentPositionEpoch != null) { + onLiveSeek(currentPositionEpoch + offsetSeconds); + } else { + final target = clampSeekPosition(player, player.state.position + Duration(seconds: offsetSeconds)); + unawaited(player.seek(target)); + } + } + switch (action) { case 'play_pause': player.playOrPause(); @@ -251,20 +266,16 @@ class KeyboardShortcutsService { _settingsService.setVolume(newVolume); break; case 'seek_forward': - final fwdTarget = clampSeekPosition(player, player.state.position + Duration(seconds: _seekTimeSmall)); - unawaited(player.seek(fwdTarget)); + performSeek(_seekTimeSmall); break; case 'seek_backward': - final bwdTarget = clampSeekPosition(player, player.state.position - Duration(seconds: _seekTimeSmall)); - unawaited(player.seek(bwdTarget)); + performSeek(-_seekTimeSmall); break; case 'seek_forward_large': - final fwdLTarget = clampSeekPosition(player, player.state.position + Duration(seconds: _seekTimeLarge)); - unawaited(player.seek(fwdLTarget)); + performSeek(_seekTimeLarge); break; case 'seek_backward_large': - final bwdLTarget = clampSeekPosition(player, player.state.position - Duration(seconds: _seekTimeLarge)); - unawaited(player.seek(bwdLTarget)); + performSeek(-_seekTimeLarge); break; case 'fullscreen_toggle': onToggleFullscreen?.call(); diff --git a/lib/widgets/video_controls/video_controls.dart b/lib/widgets/video_controls/video_controls.dart index 1a13c20d..1a69441e 100644 --- a/lib/widgets/video_controls/video_controls.dart +++ b/lib/widgets/video_controls/video_controls.dart @@ -1721,6 +1721,8 @@ class _PlexVideoControlsState extends State with WindowListen _previousChapter, onBack: widget.onBack ?? () => Navigator.of(context).pop(true), onToggleShader: _toggleShader, + currentPositionEpoch: widget.currentPositionEpoch, + onLiveSeek: widget.onLiveSeek, ); if (result == KeyEventResult.handled) { _focusNode.requestFocus(); // self-heal focus @@ -1951,6 +1953,8 @@ class _PlexVideoControlsState extends State with WindowListen onBack: widget.onBack ?? () => Navigator.of(context).pop(true), onToggleShader: _toggleShader, onSkipMarker: _performAutoSkip, + currentPositionEpoch: widget.currentPositionEpoch, + onLiveSeek: widget.onLiveSeek, ); // Let non-navigation keys (volume, etc.) pass through to the OS if (!event.logicalKey.isNavigationKey) return KeyEventResult.ignored;