Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 4 additions & 16 deletions lib/screens/video_player/video_player.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -30,27 +29,16 @@ class _VideoPlayerState extends ConsumerState<VideoPlayer> 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();
}
}
}
Expand Down
52 changes: 36 additions & 16 deletions lib/wrappers/media_control_wrapper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ class MediaControlsWrapper extends BaseAudioHandler implements VideoPlayerContro
bool _isAudioQueueMode = false;
bool _audioQueueTransitioning = false;

/// Last requested wakelock state; used to skip redundant calls during steady playback.
bool _wakelockEnabled = false;

AudioPrefetchBuffer? _prefetchBuffer;
List<ItemBaseModel> _mpvPlaylistItems = [];
int _mpvPlaylistCurrentIndex = 0;
Expand Down Expand Up @@ -259,6 +262,7 @@ class MediaControlsWrapper extends BaseAudioHandler implements VideoPlayerContro
));
smtc?.setPosition(value.position);
smtc?.setPlaybackStatus(value.playing ? PlaybackStatus.playing : PlaybackStatus.paused);
_applyWakelock(_shouldKeepScreenOn(value.playing));
if (value.completed && !_audioQueueTransitioning) {
_onAudioTrackCompleted();
}
Expand Down Expand Up @@ -308,6 +312,32 @@ class MediaControlsWrapper extends BaseAudioHandler implements VideoPlayerContro
return loadPreviousVideo();
}

/// Video needs the screen awake; audio can keep playing with the screen off.
bool _shouldKeepScreenOn(bool playing) {
final item = ref.read(playBackModel.select((value) => value?.item));
return playing && item is! AudioModel;
}

/// Single source of truth for the wakelock. Idempotent during steady
/// playback; pass [force] to re-apply even when [_wakelockEnabled] already
/// matches (needed after Android silently clears the window flag on resume).
Future<void> _applyWakelock(bool shouldEnable, {bool force = false}) async {
if (!force && shouldEnable == _wakelockEnabled) return;
_wakelockEnabled = shouldEnable;
if (shouldEnable) {
await WakelockPlus.enable();
} else {
await WakelockPlus.disable();
}
}

/// Re-applies the wakelock based on the current playback state. Called when
/// the app returns to the foreground, because Android drops the
/// keep-screen-on window flag across a background→foreground cycle and
/// nothing else restores it while playback continues.
Future<void> reassertWakelock() async =>
_applyWakelock(_shouldKeepScreenOn(_player?.lastState.playing ?? false), force: true);

@override
Future<void> pause() async {
await _player?.pause();
Expand All @@ -317,7 +347,7 @@ class MediaControlsWrapper extends BaseAudioHandler implements VideoPlayerContro
updatePosition: position,
controls: [MediaControl.play],
));
await WakelockPlus.disable();
await _applyWakelock(false);
final playerState = _player;
if (playerState != null) {
final model = ref.read(playBackModel);
Expand All @@ -331,14 +361,12 @@ class MediaControlsWrapper extends BaseAudioHandler implements VideoPlayerContro

@override
Future<void> 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) {
await WakelockPlus.enable();
}
await _applyWakelock(_shouldKeepScreenOn(true));

await _player?.play();

final playBackItem = ref.read(playBackModel.select((value) => value?.item));

final currentPosition = await ref.read(playBackModel.select((value) => value?.startDuration()));
if (_isNewPlayback || !playbackState.value.playing) {
_isNewPlayback = false;
Expand Down Expand Up @@ -446,7 +474,7 @@ class MediaControlsWrapper extends BaseAudioHandler implements VideoPlayerContro
if (playbackModel == null) return;

ref.read(mediaPlaybackProvider.notifier).update((state) => state.copyWith(state: VideoPlayerState.disposed));
WakelockPlus.disable();
_applyWakelock(false);
_player?.stop();
ref.read(windowTitleProvider.notifier).setPlayTitle(null);

Expand Down Expand Up @@ -497,15 +525,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) {
await WakelockPlus.enable();
}
} else {
await WakelockPlus.disable();
}
await _applyWakelock(_shouldKeepScreenOn(playing));

final playerState = _player;
if (playerState != null) {
Expand Down
Loading