From a7f1c8a13c77393727574d754a3bd1d13a451be3 Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Tue, 25 Nov 2025 04:03:08 +0100 Subject: [PATCH] fix: auto hide controls on mobile close #129 --- .../video_controls/video_controls.dart | 98 ++++++++++++------- 1 file changed, 65 insertions(+), 33 deletions(-) diff --git a/lib/widgets/video_controls/video_controls.dart b/lib/widgets/video_controls/video_controls.dart index 0d5b2330..fe43ce74 100644 --- a/lib/widgets/video_controls/video_controls.dart +++ b/lib/widgets/video_controls/video_controls.dart @@ -1,4 +1,4 @@ -import 'dart:async'; +import 'dart:async' show StreamSubscription, Timer; import 'dart:io' show Platform; import 'package:flutter/material.dart'; @@ -112,6 +112,8 @@ class _PlexVideoControlsState extends State PlexMarker? _currentMarker; List _markers = []; bool _markersLoaded = false; + // Playback state subscription for auto-hide timer + StreamSubscription? _playingSubscription; @override void initState() { @@ -123,6 +125,7 @@ class _PlexVideoControlsState extends State _startHideTimer(); _initKeyboardService(); _listenToPosition(); + _listenToPlayingState(); // Add lifecycle observer to reload settings when app resumes WidgetsBinding.instance.addObserver(this); // Add window listener for tracking fullscreen state (for button icon) @@ -159,6 +162,17 @@ class _PlexVideoControlsState extends State }); } + /// Listen to playback state changes to manage auto-hide timer on iOS/mobile + void _listenToPlayingState() { + _playingSubscription = widget.player.stream.playing.listen((isPlaying) { + if (isPlaying && _showControls) { + _startHideTimer(); + } else if (!isPlaying) { + _hideTimer?.cancel(); + } + }); + } + void _skipMarker() { if (_currentMarker != null) { widget.player.seek(_currentMarker!.endTime); @@ -221,6 +235,7 @@ class _PlexVideoControlsState extends State _hideTimer?.cancel(); _feedbackTimer?.cancel(); _seekThrottleTimer?.cancel(); + _playingSubscription?.cancel(); _focusNode.dispose(); // Remove lifecycle observer WidgetsBinding.instance.removeObserver(this); @@ -295,6 +310,13 @@ class _PlexVideoControlsState extends State } } + /// Restart the hide timer on user interaction (if video is playing) + void _restartHideTimerIfPlaying() { + if (widget.player.state.playing) { + _startHideTimer(); + } + } + void _toggleControls() { setState(() { _showControls = !_showControls; @@ -635,40 +657,50 @@ class _PlexVideoControlsState extends State ), ), child: isMobile - ? MobileVideoControls( - player: widget.player, - metadata: widget.metadata, - chapters: _chapters, - chaptersLoaded: _chaptersLoaded, - seekTimeSmall: _seekTimeSmall, - trackChapterControls: - _buildTrackChapterControlsWidget(), - onSeek: _throttledSeek, - onSeekEnd: _finalizeSeek, - onPlayPause: - () {}, // Not used, handled internally - onCancelAutoHide: () => _hideTimer?.cancel(), - onStartAutoHide: _startHideTimer, - ) - : DesktopVideoControls( - player: widget.player, - metadata: widget.metadata, - onNext: widget.onNext, - onPrevious: widget.onPrevious, - chapters: _chapters, - chaptersLoaded: _chaptersLoaded, - seekTimeSmall: _seekTimeSmall, - volumeControl: VolumeControl( + ? Listener( + behavior: HitTestBehavior.translucent, + onPointerDown: (_) => + _restartHideTimerIfPlaying(), + child: MobileVideoControls( player: widget.player, + metadata: widget.metadata, + chapters: _chapters, + chaptersLoaded: _chaptersLoaded, + seekTimeSmall: _seekTimeSmall, + trackChapterControls: + _buildTrackChapterControlsWidget(), + onSeek: _throttledSeek, + onSeekEnd: _finalizeSeek, + onPlayPause: + () {}, // Not used, handled internally + onCancelAutoHide: () => _hideTimer?.cancel(), + onStartAutoHide: _startHideTimer, + ), + ) + : Listener( + behavior: HitTestBehavior.translucent, + onPointerDown: (_) => + _restartHideTimerIfPlaying(), + child: DesktopVideoControls( + player: widget.player, + metadata: widget.metadata, + onNext: widget.onNext, + onPrevious: widget.onPrevious, + chapters: _chapters, + chaptersLoaded: _chaptersLoaded, + seekTimeSmall: _seekTimeSmall, + volumeControl: VolumeControl( + player: widget.player, + ), + trackChapterControls: + _buildTrackChapterControlsWidget(), + onSeekToPreviousChapter: _seekToPreviousChapter, + onSeekToNextChapter: _seekToNextChapter, + onSeek: _throttledSeek, + onSeekEnd: _finalizeSeek, + getReplayIcon: getReplayIcon, + getForwardIcon: getForwardIcon, ), - trackChapterControls: - _buildTrackChapterControlsWidget(), - onSeekToPreviousChapter: _seekToPreviousChapter, - onSeekToNextChapter: _seekToNextChapter, - onSeek: _throttledSeek, - onSeekEnd: _finalizeSeek, - getReplayIcon: getReplayIcon, - getForwardIcon: getForwardIcon, ), ), ),