diff --git a/lib/screens/video_player/video_player.dart b/lib/screens/video_player/video_player.dart index 384287531..b6091af35 100644 --- a/lib/screens/video_player/video_player.dart +++ b/lib/screens/video_player/video_player.dart @@ -9,7 +9,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:fladder/models/media_playback_model.dart'; import 'package:fladder/models/playback/playback_model.dart'; import 'package:fladder/models/playback/tv_playback_model.dart'; -import 'package:fladder/providers/pip_provider.dart'; import 'package:fladder/providers/settings/video_player_settings_provider.dart'; import 'package:fladder/providers/video_player_provider.dart'; import 'package:fladder/screens/video_player/components/video_player_guide_wrapper.dart'; @@ -31,27 +30,16 @@ class _VideoPlayerState extends ConsumerState with WidgetsBindingOb double lastScale = 0.0; bool errorPlaying = false; - bool playing = false; late PlaybackModel? currentPlaybackModel = ref.read(playBackModel); @override void didChangeAppLifecycleState(AppLifecycleState state) { - //Don't pause on desktop focus loss + //Don't manage the wakelock on desktop focus loss if (!(AdaptiveLayout.of(context).isDesktop || kIsWeb)) { - // Don't pause when entering PiP — playback must continue. - final inPip = ref.read(pipStateProvider).asData?.value ?? false; - switch (state) { - case AppLifecycleState.resumed: - if (playing) ref.read(videoPlayerProvider).play(); - break; - case AppLifecycleState.hidden: - case AppLifecycleState.paused: - case AppLifecycleState.detached: - if (playing && !inPip) ref.read(videoPlayerProvider).pause(); - break; - default: - break; + if (state == AppLifecycleState.resumed) { + // Android drops the keep-screen-on flag on resume; re-apply it. + ref.read(videoPlayerProvider).reassertWakelock(); } } } diff --git a/lib/wrappers/media_control_wrapper.dart b/lib/wrappers/media_control_wrapper.dart index 2300e3afb..e5d4cb78b 100644 --- a/lib/wrappers/media_control_wrapper.dart +++ b/lib/wrappers/media_control_wrapper.dart @@ -74,6 +74,7 @@ class MediaControlsWrapper extends BaseAudioHandler implements VideoPlayerContro bool _isNewPlayback = false; bool _isAudioQueueMode = false; bool _audioQueueTransitioning = false; + bool _wakelockEnabled = false; AudioPrefetchBuffer? _prefetchBuffer; List _mpvPlaylistItems = []; @@ -264,6 +265,7 @@ class MediaControlsWrapper extends BaseAudioHandler implements VideoPlayerContro )); smtc?.setPosition(value.position); smtc?.setPlaybackStatus(value.playing ? PlaybackStatus.playing : PlaybackStatus.paused); + unawaited(_applyWakelock(_shouldKeepScreenOn(value.playing))); if (value.completed && !_audioQueueTransitioning) { _onAudioTrackCompleted(); } @@ -313,6 +315,26 @@ class MediaControlsWrapper extends BaseAudioHandler implements VideoPlayerContro return loadPreviousVideo(); } + bool _shouldKeepScreenOn(bool playing) { + final item = ref.read(playBackModel.select((value) => value?.item)); + return playing && item is! AudioModel; + } + + /// [force] re-applies even when the cached state already matches, since + /// Android silently clears the keep-screen-on flag while we still think it's set. + Future _applyWakelock(bool shouldEnable, {bool force = false}) async { + if (!force && shouldEnable == _wakelockEnabled) return; + _wakelockEnabled = shouldEnable; + if (shouldEnable) { + await WakelockPlus.enable(); + } else { + await WakelockPlus.disable(); + } + } + + Future reassertWakelock() async => + _applyWakelock(_shouldKeepScreenOn(_player?.lastState.playing ?? false), force: true); + @override Future pause() async { await _player?.pause(); @@ -322,7 +344,7 @@ class MediaControlsWrapper extends BaseAudioHandler implements VideoPlayerContro updatePosition: position, controls: [MediaControl.play], )); - unawaited(WakelockPlus.disable()); + unawaited(_applyWakelock(false)); final playerState = _player; if (playerState != null) { final model = ref.read(playBackModel); @@ -336,13 +358,11 @@ class MediaControlsWrapper extends BaseAudioHandler implements VideoPlayerContro @override Future play() async { - // Only enable wakelock for video; audio can continue with screen off final playBackItem = ref.read(playBackModel.select((value) => value?.item)); - if (playBackItem is! AudioModel) { - unawaited(WakelockPlus.enable()); - } else { + if (playBackItem is AudioModel) { _isStopped = false; } + unawaited(_applyWakelock(_shouldKeepScreenOn(true))); await _player?.play(); @@ -456,7 +476,7 @@ class MediaControlsWrapper extends BaseAudioHandler implements VideoPlayerContro _isStopped = true; ref.read(mediaPlaybackProvider.notifier).update((state) => state.copyWith(state: VideoPlayerState.disposed)); - unawaited(WakelockPlus.disable()); + unawaited(_applyWakelock(false)); _player?.stop(); ref.read(windowTitleProvider.notifier).setPlayTitle(null); @@ -510,15 +530,7 @@ class MediaControlsWrapper extends BaseAudioHandler implements VideoPlayerContro controls: [playing ? MediaControl.pause : MediaControl.play], )); - if (playing) { - // Only enable wakelock for video; audio can continue with screen off - final playBackItem = ref.read(playBackModel.select((value) => value?.item)); - if (playBackItem is! AudioModel) { - unawaited(WakelockPlus.enable()); - } - } else { - unawaited(WakelockPlus.disable()); - } + unawaited(_applyWakelock(_shouldKeepScreenOn(playing))); final playerState = _player; if (playerState != null) {