fix: reduce main-thread pressure during playback

This commit is contained in:
edde746
2026-02-15 18:35:51 +01:00
parent b62a3501d2
commit eaad505d96
3 changed files with 31 additions and 13 deletions
+8 -1
View File
@@ -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;
+22 -11
View File
@@ -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<void> _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)
@@ -403,7 +403,7 @@ class _PlexVideoControlsState extends State<PlexVideoControls> 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;