From 5fac088e2d1727cd206e1d9191eff022c933c61c Mon Sep 17 00:00:00 2001 From: PartyDonut Date: Mon, 28 Jul 2025 21:32:37 +0200 Subject: [PATCH] fix: Move calculation logic to lib_mpv subtitles --- .../settings/subtitle_settings_model.dart | 6 ++- .../video_player/video_player_controls.dart | 34 +------------- lib/stubs/web/lib_mdk_web.dart | 2 +- lib/util/subtitle_position_calculator.dart | 10 +---- lib/wrappers/media_control_wrapper.dart | 4 +- lib/wrappers/players/base_player.dart | 2 +- lib/wrappers/players/lib_mdk.dart | 2 +- lib/wrappers/players/lib_mpv.dart | 45 ++++++++++++------- 8 files changed, 42 insertions(+), 63 deletions(-) diff --git a/lib/models/settings/subtitle_settings_model.dart b/lib/models/settings/subtitle_settings_model.dart index 77e1ebf..2cff918 100644 --- a/lib/models/settings/subtitle_settings_model.dart +++ b/lib/models/settings/subtitle_settings_model.dart @@ -216,7 +216,8 @@ class SubtitleText extends ConsumerWidget { child: Stack( alignment: Alignment.bottomCenter, children: [ - Positioned( + AnimatedPositioned( + duration: const Duration(milliseconds: 125), bottom: position, child: Container( constraints: BoxConstraints(maxWidth: constraints.maxWidth, maxHeight: constraints.maxHeight), @@ -234,7 +235,8 @@ class SubtitleText extends ConsumerWidget { ), ), ), - Positioned( + AnimatedPositioned( + duration: const Duration(milliseconds: 125), bottom: position, child: Container( constraints: BoxConstraints(maxWidth: constraints.maxWidth, maxHeight: constraints.maxHeight), diff --git a/lib/screens/video_player/video_player_controls.dart b/lib/screens/video_player/video_player_controls.dart index 592736a..2741714 100644 --- a/lib/screens/video_player/video_player_controls.dart +++ b/lib/screens/video_player/video_player_controls.dart @@ -41,9 +41,7 @@ class DesktopControls extends ConsumerStatefulWidget { } class _DesktopControlsState extends ConsumerState { - // Add GlobalKey to measure bottom controls height final GlobalKey _bottomControlsKey = GlobalKey(); - double? _cachedMenuHeight; late RestartableTimer timer = RestartableTimer( const Duration(seconds: 5), @@ -112,41 +110,11 @@ class _DesktopControlsState extends ConsumerState { timer.reset(); } - // Height measurement logic remains here for architectural reasons: - // 1. The video controls widget owns and renders the bottom menu UI elements - // 2. Only this widget has direct access to the menu's RenderBox for accurate measurement - // 3. Subtitle widgets are separate components that shouldn't know about control UI structure - // 4. Different players (LibMPV, MDK) can receive the same measurement without duplicating logic - // 5. Clean separation: controls handle UI measurement, players handle subtitle positioning - // Use PostFrameCallback to measure height after layout - void _measureMenuHeight() { - WidgetsBinding.instance.addPostFrameCallback((_) { - if (!mounted) return; - - final RenderBox? renderBox = _bottomControlsKey.currentContext?.findRenderObject() as RenderBox?; - final newHeight = renderBox?.size.height; - - if (newHeight != _cachedMenuHeight && newHeight != null) { - setState(() { - _cachedMenuHeight = newHeight; - }); - } - }); - } - - // Method to get actual menu height - double? getBottomControlsHeight() { - return _cachedMenuHeight; - } - @override Widget build(BuildContext context) { - // Trigger measurement after each build to ensure accurate height - _measureMenuHeight(); - final mediaSegments = ref.watch(playBackModel.select((value) => value?.mediaSegments)); final player = ref.watch(videoPlayerProvider); - final subtitleWidget = player.subtitleWidget(showOverlay, menuHeight: getBottomControlsHeight()); + final subtitleWidget = player.subtitleWidget(showOverlay, controlsKey: _bottomControlsKey); return InputHandler( autoFocus: false, onKeyEvent: (node, event) => _onKey(event) ? KeyEventResult.handled : KeyEventResult.ignored, diff --git a/lib/stubs/web/lib_mdk_web.dart b/lib/stubs/web/lib_mdk_web.dart index abb2e17..a756fbd 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, {double? menuHeight}) => null; + Widget? subtitles(bool showOverlay, {GlobalKey? menuKey}) => null; @override Future setVolume(double volume) async {} diff --git a/lib/util/subtitle_position_calculator.dart b/lib/util/subtitle_position_calculator.dart index 5cbecba..7617ddf 100644 --- a/lib/util/subtitle_position_calculator.dart +++ b/lib/util/subtitle_position_calculator.dart @@ -1,12 +1,9 @@ import 'dart:math' as math; + import 'package:fladder/models/settings/subtitle_settings_model.dart'; class SubtitlePositionCalculator { static const double _fallbackMenuHeightPercentage = 0.15; - static const double _dynamicSubtitlePadding = - 0.00; // Currently unused (0%). Reserved for future implementation of a user-adjustable slider to control subtitle positioning - // relative to the player menu - static const double _fallbackSubtitlePadding = 0.01; // 1% padding for conservative fallback positioning static const double _maxSubtitleOffset = 0.85; static double calculateOffset({ @@ -20,17 +17,14 @@ class SubtitlePositionCalculator { } double menuHeightPercentage; - double subtitlePadding; if (menuHeight != null && screenHeight > 0) { menuHeightPercentage = menuHeight / screenHeight; - subtitlePadding = _dynamicSubtitlePadding; } else { menuHeightPercentage = _fallbackMenuHeightPercentage; - subtitlePadding = _fallbackSubtitlePadding; } - final minSafeOffset = menuHeightPercentage + subtitlePadding; + final minSafeOffset = menuHeightPercentage; if (settings.verticalOffset >= minSafeOffset) { return math.min(settings.verticalOffset, _maxSubtitleOffset); diff --git a/lib/wrappers/media_control_wrapper.dart b/lib/wrappers/media_control_wrapper.dart index 37d48db..645fb91 100644 --- a/lib/wrappers/media_control_wrapper.dart +++ b/lib/wrappers/media_control_wrapper.dart @@ -39,8 +39,8 @@ class MediaControlsWrapper extends BaseAudioHandler { Stream? get stateStream => _player?.stateStream; PlayerState? get lastState => _player?.lastState; - Widget? subtitleWidget(bool showOverlay, {double? menuHeight}) => - _player?.subtitles(showOverlay, menuHeight: menuHeight); + Widget? subtitleWidget(bool showOverlay, {GlobalKey? controlsKey}) => + _player?.subtitles(showOverlay, menuKey: controlsKey); 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 f999548..686ecb5 100644 --- a/lib/wrappers/players/base_player.dart +++ b/lib/wrappers/players/base_player.dart @@ -20,7 +20,7 @@ abstract class BasePlayer { ); Widget? subtitles( bool showOverlay, { - double? menuHeight, + GlobalKey? menuKey, }); Future dispose(); Future open(String url, bool play); diff --git a/lib/wrappers/players/lib_mdk.dart b/lib/wrappers/players/lib_mdk.dart index 056ab76..90d2c76 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, {double? menuHeight}) => null; + Widget? subtitles(bool showOverlay, {GlobalKey? menuKey}) => 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 4d17d8d..ebdf233 100644 --- a/lib/wrappers/players/lib_mpv.dart +++ b/lib/wrappers/players/lib_mpv.dart @@ -168,13 +168,13 @@ class LibMPV extends BasePlayer { @override Widget? subtitles( bool showOverlay, { - double? menuHeight, // Passed from video_player_controls.dart which owns the menu UI + GlobalKey? menuKey, }) => _controller != null ? _VideoSubtitles( controller: _controller!, showOverlay: showOverlay, - menuHeight: menuHeight, // Forward the measured height for accurate positioning + menuKey: menuKey, ) : null; @@ -198,12 +198,12 @@ class LibMPV extends BasePlayer { class _VideoSubtitles extends ConsumerStatefulWidget { final VideoController controller; final bool showOverlay; - final double? menuHeight; // Accurate measurement from controls, null triggers fallback positioning + final GlobalKey? menuKey; const _VideoSubtitles({ required this.controller, this.showOverlay = false, - this.menuHeight, // Receives pre-measured height rather than measuring internally + this.menuKey, }); @override @@ -216,15 +216,16 @@ class _VideoSubtitlesState extends ConsumerState<_VideoSubtitles> { List? _lastSubtitleList; StreamSubscription>? subscription; + double? _cachedMenuHeight; + @override void initState() { - super.initState(); // Move to very start as per best practices + super.initState(); subtitle = widget.controller.player.state.subtitle; subscription = widget.controller.player.stream.subtitle.listen((value) { if (mounted) { setState(() { subtitle = value; - // Invalidate cache when subtitle changes _lastSubtitleList = null; }); } @@ -239,30 +240,29 @@ class _VideoSubtitlesState extends ConsumerState<_VideoSubtitles> { @override Widget build(BuildContext context) { - final settings = ref.watch(subtitleSettingsProvider); - final padding = MediaQuery.of(context).padding; + _measureMenuHeight(); + + final settings = ref.watch(subtitleSettingsProvider); + final padding = MediaQuery.paddingOf(context); - // Cache processed subtitle text to avoid unnecessary computation if (!const ListEquality().equals(subtitle, _lastSubtitleList)) { - _cachedSubtitleText = subtitle.where((line) => line.trim().isNotEmpty).map((line) => line.trim()).join('\n'); _lastSubtitleList = List.from(subtitle); + _cachedSubtitleText = subtitle.where((line) => line.trim().isNotEmpty).map((line) => line.trim()).join('\n'); } + final text = _cachedSubtitleText; - // Extract libass enabled check for clarity final bool isLibassEnabled = widget.controller.player.platform?.configuration.libass ?? false; - // Early return for cases where subtitles shouldn't be rendered if (isLibassEnabled || text.isEmpty) { return const SizedBox.shrink(); } - // Use the utility for offset calculation with passed menuHeight final offset = SubtitlePositionCalculator.calculateOffset( settings: settings, showOverlay: widget.showOverlay, - screenHeight: MediaQuery.of(context).size.height, - menuHeight: widget.menuHeight, + screenHeight: MediaQuery.sizeOf(context).height, + menuHeight: _cachedMenuHeight, ); return SubtitleText( @@ -272,4 +272,19 @@ class _VideoSubtitlesState extends ConsumerState<_VideoSubtitles> { text: text, ); } + + void _measureMenuHeight() { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (!mounted || widget.menuKey == null) return; + + final RenderBox? renderBox = widget.menuKey?.currentContext?.findRenderObject() as RenderBox?; + final newHeight = renderBox?.size.height; + + if (newHeight != _cachedMenuHeight && newHeight != null) { + setState(() { + _cachedMenuHeight = newHeight; + }); + } + }); + } }