From eaad505d9667a58209adaff380d09e0753773537 Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Sun, 15 Feb 2026 18:35:51 +0100 Subject: [PATCH] fix: reduce main-thread pressure during playback --- lib/mpv/player/player_base.dart | 9 ++++- lib/services/playback_progress_tracker.dart | 33 ++++++++++++------- .../video_controls/video_controls.dart | 2 +- 3 files changed, 31 insertions(+), 13 deletions(-) diff --git a/lib/mpv/player/player_base.dart b/lib/mpv/player/player_base.dart index 3604788f..6b73ae9d 100644 --- a/lib/mpv/player/player_base.dart +++ b/lib/mpv/player/player_base.dart @@ -37,6 +37,7 @@ abstract class PlayerBase with PlayerStreamControllersMixin implements Player { StreamSubscription? _eventSubscription; bool _disposed = false; + DateTime? _lastPositionEmit; /// Whether the player has been initialized. /// Subclasses should set this to true after initialization. @@ -128,7 +129,13 @@ abstract class PlayerBase with PlayerStreamControllersMixin implements Player { if (value is num) { final position = Duration(milliseconds: (value * 1000).toInt()); _state = _state.copyWith(position: position); - positionController.add(position); + // Throttle stream emissions to ~4Hz (250ms) to reduce listener/rebuild pressure. + // _state is always updated above so synchronous reads stay current. + final now = DateTime.now(); + if (_lastPositionEmit == null || now.difference(_lastPositionEmit!).inMilliseconds >= 250) { + _lastPositionEmit = now; + positionController.add(position); + } } break; diff --git a/lib/services/playback_progress_tracker.dart b/lib/services/playback_progress_tracker.dart index 80cd335f..4fad36fc 100644 --- a/lib/services/playback_progress_tracker.dart +++ b/lib/services/playback_progress_tracker.dart @@ -112,17 +112,24 @@ class PlaybackProgressTracker { if (isOffline) { // Queue progress update for later sync await _sendOfflineProgress(position, duration); - } else { - // Send progress to server immediately + } else if (state == 'stopped') { + // Stopped must complete before disposal await _sendOnlineProgress(state, position, duration); - // Success — reset backoff state - if (_consecutiveFailures > 0) { + _resetBackoff(); + } else { + // Fire-and-forget for playing/paused — avoid blocking the Dart event loop + _sendOnlineProgress(state, position, duration).then((_) { + _resetBackoff(); + }).catchError((Object e) { + _consecutiveFailures++; + // Exponential backoff: skip 1, 2, 4, 8... ticks (capped at 6 ≈ 60s) + _ticksToSkip = (1 << (_consecutiveFailures - 1)).clamp(1, 6); appLogger.d( - 'Progress update succeeded after $_consecutiveFailures consecutive failure(s), resetting backoff', + 'Progress update failed ($_consecutiveFailures consecutive), ' + 'skipping next $_ticksToSkip tick(s)', + error: e, ); - _consecutiveFailures = 0; - _ticksToSkip = 0; - } + }); } // Emit watch state event on stop for UI updates across screens @@ -136,7 +143,6 @@ class PlaybackProgressTracker { } catch (e) { if (!isOffline) { _consecutiveFailures++; - // Exponential backoff: skip 1, 2, 4, 8... ticks (capped at 6 ≈ 60s) _ticksToSkip = (1 << (_consecutiveFailures - 1)).clamp(1, 6); appLogger.d( 'Progress update failed ($_consecutiveFailures consecutive), ' @@ -149,6 +155,13 @@ class PlaybackProgressTracker { } } + void _resetBackoff() { + if (_consecutiveFailures > 0) { + _consecutiveFailures = 0; + _ticksToSkip = 0; + } + } + /// Send progress update to Plex server (online mode) Future _sendOnlineProgress(String state, Duration position, Duration duration) async { await client!.updateProgress( @@ -157,8 +170,6 @@ class PlaybackProgressTracker { state: state, duration: duration.inMilliseconds, ); - - appLogger.d('Progress update sent: $state at ${position.inSeconds}s / ${duration.inSeconds}s'); } /// Queue progress update locally (offline mode) diff --git a/lib/widgets/video_controls/video_controls.dart b/lib/widgets/video_controls/video_controls.dart index 75dc23a8..2aca1036 100644 --- a/lib/widgets/video_controls/video_controls.dart +++ b/lib/widgets/video_controls/video_controls.dart @@ -403,7 +403,7 @@ class _PlexVideoControlsState extends State with WindowListen if (!shouldAutoSkip || _autoSkipDelay <= 0) return; _autoSkipProgress = 0.0; - const tickDuration = Duration(milliseconds: 50); + const tickDuration = Duration(milliseconds: 200); final totalTicks = (_autoSkipDelay * 1000) / tickDuration.inMilliseconds; if (totalTicks <= 0) return;