mirror of
https://github.com/gabehf/Fladder.git
synced 2026-03-07 21:48:14 -08:00
fix: Move calculation logic to lib_mpv subtitles
This commit is contained in:
parent
c446210e6a
commit
5fac088e2d
8 changed files with 42 additions and 63 deletions
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -41,9 +41,7 @@ class DesktopControls extends ConsumerStatefulWidget {
|
|||
}
|
||||
|
||||
class _DesktopControlsState extends ConsumerState<DesktopControls> {
|
||||
// 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<DesktopControls> {
|
|||
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,
|
||||
|
|
|
|||
|
|
@ -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<void> setVolume(double volume) async {}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -39,8 +39,8 @@ class MediaControlsWrapper extends BaseAudioHandler {
|
|||
Stream<PlayerState>? 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;
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ abstract class BasePlayer {
|
|||
);
|
||||
Widget? subtitles(
|
||||
bool showOverlay, {
|
||||
double? menuHeight,
|
||||
GlobalKey? menuKey,
|
||||
});
|
||||
Future<void> dispose();
|
||||
Future<void> open(String url, bool play);
|
||||
|
|
|
|||
|
|
@ -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<void> setVolume(double volume) async => _controller?.setVolume(volume / 100);
|
||||
|
|
|
|||
|
|
@ -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<String>? _lastSubtitleList;
|
||||
StreamSubscription<List<String>>? 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<String>.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;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue