feat: Enhance subtitle handling with dynamic menu height adjustment

This commit is contained in:
Kirill Boychenko 2025-07-28 02:13:00 +02:00
parent d60522b021
commit 1fdab92f1f
6 changed files with 47 additions and 20 deletions

View file

@ -41,6 +41,9 @@ class DesktopControls extends ConsumerStatefulWidget {
}
class _DesktopControlsState extends ConsumerState<DesktopControls> {
// 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<DesktopControls> {
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<DesktopControls> {
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,

View file

@ -64,7 +64,7 @@ class LibMDK extends BasePlayer {
null;
@override
Widget? subtitles(bool showOverlay) => null;
Widget? subtitles(bool showOverlay, {double? menuHeight}) => null;
@override
Future<void> setVolume(double volume) async {}

View file

@ -39,7 +39,8 @@ class MediaControlsWrapper extends BaseAudioHandler {
Stream<PlayerState>? 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;

View file

@ -19,8 +19,9 @@ abstract class BasePlayer {
BoxFit fit,
);
Widget? subtitles(
bool showOverlay,
);
bool showOverlay, {
double? menuHeight,
});
Future<void> dispose();
Future<void> open(String url, bool play);
Future<void> seek(Duration position);

View file

@ -191,7 +191,7 @@ class LibMDK extends BasePlayer {
);
@override
Widget? subtitles(bool showOverlay) => null;
Widget? subtitles(bool showOverlay, {double? menuHeight}) => null;
@override
Future<void> setVolume(double volume) async => _controller?.setVolume(volume / 100);

View file

@ -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<String> 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