From 1fdab92f1f72c53f71c172eeecf6c9ba863a30e2 Mon Sep 17 00:00:00 2001 From: Kirill Boychenko Date: Mon, 28 Jul 2025 02:13:00 +0200 Subject: [PATCH] feat: Enhance subtitle handling with dynamic menu height adjustment --- .../video_player/video_player_controls.dart | 12 +++++- lib/stubs/web/lib_mdk_web.dart | 2 +- lib/wrappers/media_control_wrapper.dart | 3 +- lib/wrappers/players/base_player.dart | 5 ++- lib/wrappers/players/lib_mdk.dart | 2 +- lib/wrappers/players/lib_mpv.dart | 43 +++++++++++++------ 6 files changed, 47 insertions(+), 20 deletions(-) diff --git a/lib/screens/video_player/video_player_controls.dart b/lib/screens/video_player/video_player_controls.dart index 4216b0f..ed0593a 100644 --- a/lib/screens/video_player/video_player_controls.dart +++ b/lib/screens/video_player/video_player_controls.dart @@ -41,6 +41,9 @@ class DesktopControls extends ConsumerStatefulWidget { } class _DesktopControlsState extends ConsumerState { + // Add GlobalKey to measure bottom controls height + final GlobalKey _bottomControlsKey = GlobalKey(); + late RestartableTimer timer = RestartableTimer( const Duration(seconds: 5), () => mounted ? toggleOverlay(value: false) : null, @@ -108,11 +111,17 @@ class _DesktopControlsState extends ConsumerState { timer.reset(); } + // Method to get actual menu height + double? getBottomControlsHeight() { + final RenderBox? renderBox = _bottomControlsKey.currentContext?.findRenderObject() as RenderBox?; + return renderBox?.size.height; + } + @override Widget build(BuildContext context) { final mediaSegments = ref.watch(playBackModel.select((value) => value?.mediaSegments)); final player = ref.watch(videoPlayerProvider); - final subtitleWidget = player.subtitleWidget(showOverlay); + final subtitleWidget = player.subtitleWidget(showOverlay, menuHeight: getBottomControlsHeight()); return InputHandler( autoFocus: false, onKeyEvent: (node, event) => _onKey(event) ? KeyEventResult.handled : KeyEventResult.ignored, @@ -293,6 +302,7 @@ class _DesktopControlsState extends ConsumerState { final mediaPlayback = ref.watch(mediaPlaybackProvider); final bitRateOptions = ref.watch(playBackModel.select((value) => value?.bitRateOptions)); return Container( + key: _bottomControlsKey, // Add key to measure height decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.bottomCenter, diff --git a/lib/stubs/web/lib_mdk_web.dart b/lib/stubs/web/lib_mdk_web.dart index 3e3a790..abb2e17 100644 --- a/lib/stubs/web/lib_mdk_web.dart +++ b/lib/stubs/web/lib_mdk_web.dart @@ -64,7 +64,7 @@ class LibMDK extends BasePlayer { null; @override - Widget? subtitles(bool showOverlay) => null; + Widget? subtitles(bool showOverlay, {double? menuHeight}) => null; @override Future setVolume(double volume) async {} diff --git a/lib/wrappers/media_control_wrapper.dart b/lib/wrappers/media_control_wrapper.dart index 5ad95c6..37d48db 100644 --- a/lib/wrappers/media_control_wrapper.dart +++ b/lib/wrappers/media_control_wrapper.dart @@ -39,7 +39,8 @@ class MediaControlsWrapper extends BaseAudioHandler { Stream? get stateStream => _player?.stateStream; PlayerState? get lastState => _player?.lastState; - Widget? subtitleWidget(bool showOverlay) => _player?.subtitles(showOverlay); + Widget? subtitleWidget(bool showOverlay, {double? menuHeight}) => + _player?.subtitles(showOverlay, menuHeight: menuHeight); Widget? videoWidget(Key key, BoxFit fit) => _player?.videoWidget(key, fit); final Ref ref; diff --git a/lib/wrappers/players/base_player.dart b/lib/wrappers/players/base_player.dart index 76240e4..f999548 100644 --- a/lib/wrappers/players/base_player.dart +++ b/lib/wrappers/players/base_player.dart @@ -19,8 +19,9 @@ abstract class BasePlayer { BoxFit fit, ); Widget? subtitles( - bool showOverlay, - ); + bool showOverlay, { + double? menuHeight, + }); Future dispose(); Future open(String url, bool play); Future seek(Duration position); diff --git a/lib/wrappers/players/lib_mdk.dart b/lib/wrappers/players/lib_mdk.dart index 2ee7df8..056ab76 100644 --- a/lib/wrappers/players/lib_mdk.dart +++ b/lib/wrappers/players/lib_mdk.dart @@ -191,7 +191,7 @@ class LibMDK extends BasePlayer { ); @override - Widget? subtitles(bool showOverlay) => null; + Widget? subtitles(bool showOverlay, {double? menuHeight}) => null; @override Future setVolume(double volume) async => _controller?.setVolume(volume / 100); diff --git a/lib/wrappers/players/lib_mpv.dart b/lib/wrappers/players/lib_mpv.dart index fa0e590..759af30 100644 --- a/lib/wrappers/players/lib_mpv.dart +++ b/lib/wrappers/players/lib_mpv.dart @@ -167,12 +167,14 @@ class LibMPV extends BasePlayer { @override Widget? subtitles( - bool showOverlay, - ) => + bool showOverlay, { + double? menuHeight, + }) => _controller != null ? _VideoSubtitles( controller: _controller!, showOverlay: showOverlay, + menuHeight: menuHeight, ) : null; @@ -196,10 +198,12 @@ class LibMPV extends BasePlayer { class _VideoSubtitles extends ConsumerStatefulWidget { final VideoController controller; final bool showOverlay; + final double? menuHeight; const _VideoSubtitles({ required this.controller, this.showOverlay = false, + this.menuHeight, }); @override @@ -207,9 +211,9 @@ class _VideoSubtitles extends ConsumerStatefulWidget { } class _VideoSubtitlesState extends ConsumerState<_VideoSubtitles> { - // Promote constants to static for better readability and flexibility - static const double _menuAreaThreshold = 0.15; // Bottom 15% typically contains controls - static const double _menuAvoidanceOffset = 0.1; // Move up by 10% when needed + // Keep fallback constants for when dynamic height isn't available + static const double _fallbackMenuHeightPercentage = 0.15; // 15% fallback + static const double _subtitlePadding = 0.005; // 0.5% padding above menu static const double _maxSubtitleOffset = 0.85; // Max 85% up from bottom late List subtitle = widget.controller.player.state.subtitle; @@ -233,23 +237,34 @@ class _VideoSubtitlesState extends ConsumerState<_VideoSubtitles> { super.dispose(); } - /// Calculate subtitle offset based on menu visibility + /// Calculate subtitle offset using actual menu height when available double _calculateSubtitleOffset(SubtitleSettingsModel settings) { if (!widget.showOverlay) { return settings.verticalOffset; } - // If subtitles are already positioned above the menu area, leave them alone - if (settings.verticalOffset >= _menuAreaThreshold) { + final screenHeight = MediaQuery.of(context).size.height; + double menuHeightPercentage; + + if (widget.menuHeight != null && screenHeight > 0) { + // Convert menu height to percentage (without extra padding here) + menuHeightPercentage = widget.menuHeight! / screenHeight; + } else { + // Fallback to static percentage + menuHeightPercentage = _fallbackMenuHeightPercentage; + } + + // Calculate the minimum safe position (menu height + small padding) + final minSafeOffset = menuHeightPercentage + _subtitlePadding; + + // If subtitles are already positioned above the safe area, leave them alone + if (settings.verticalOffset >= minSafeOffset) { return settings.verticalOffset; } - // When menu is visible and subtitles are in the menu area, - // move them up slightly to avoid overlap - final adjustedOffset = settings.verticalOffset + _menuAvoidanceOffset; - - // Clamp to reasonable bounds (don't go too high or too low) - return math.max(0.0, math.min(adjustedOffset, _maxSubtitleOffset)); + // Instead of replacing user offset, use the minimum safe position + // This ensures subtitles are just above the menu, not way up high + return math.max(minSafeOffset, math.min(settings.verticalOffset, _maxSubtitleOffset)); } @override