fix: close async lifecycle races

This commit is contained in:
edde746
2026-07-12 08:42:19 +02:00
parent 6d94880c2f
commit a63369c247
3 changed files with 28 additions and 13 deletions
+13 -7
View File
@@ -53,6 +53,7 @@ class DiscordRPCService {
Duration? _mediaDuration;
Duration? _currentPosition;
double _playbackSpeed = 1.0;
int _playbackRevision = 0;
Timer? _reconnectTimer;
DateTime? _lastPresenceUpdate;
StreamSubscription<void>? _readySubscription;
@@ -106,6 +107,7 @@ class DiscordRPCService {
/// thumbnail upload uses the neutral [MediaServerClient.thumbnailUrl] /
/// [MediaServerClient.streamHeaders] surface.
Future<void> startPlayback(MediaItem metadata, MediaServerClient client) async {
final revision = ++_playbackRevision;
_currentMetadata = metadata;
_currentClient = client;
_playbackStartTime = DateTime.now();
@@ -116,7 +118,7 @@ class DiscordRPCService {
if (_isEnabled && _isConnected) {
// Upload thumbnail in background, don't block playback
unawaited(_uploadThumbnailAndUpdatePresence());
unawaited(_uploadThumbnailAndUpdatePresence(revision, metadata, client));
}
}
@@ -172,6 +174,7 @@ class DiscordRPCService {
}
Future<void> stopPlayback() async {
_playbackRevision++;
_currentMetadata = null;
_currentClient = null;
_playbackStartTime = null;
@@ -211,8 +214,10 @@ class DiscordRPCService {
await Future.delayed(const Duration(milliseconds: 200));
// Update presence if we have active playback
if (_currentMetadata != null) {
await _uploadThumbnailAndUpdatePresence();
final metadata = _currentMetadata;
final client = _currentClient;
if (metadata != null && client != null) {
await _uploadThumbnailAndUpdatePresence(_playbackRevision, metadata, client);
}
});
@@ -276,11 +281,12 @@ class DiscordRPCService {
});
}
Future<void> _uploadThumbnailAndUpdatePresence() async {
// Try to upload thumbnail, but don't block on failure
if (_cachedThumbnailUrl == null && _currentMetadata != null && _currentClient != null) {
_cachedThumbnailUrl = await _uploadThumbnail(_currentMetadata!, _currentClient!);
Future<void> _uploadThumbnailAndUpdatePresence(int revision, MediaItem metadata, MediaServerClient client) async {
final thumbnailUrl = await _uploadThumbnail(metadata, client);
if (revision != _playbackRevision || !identical(_currentMetadata, metadata) || !identical(_currentClient, client)) {
return;
}
_cachedThumbnailUrl = thumbnailUrl;
await _updatePresence();
}
@@ -300,7 +300,6 @@ class WatchTogetherPeerService with KeepaliveMixin {
try {
final channel = await _connectToRelay();
_channel = channel;
_reconnectAttempts = 0;
final completer = Completer<void>();
_listenToChannel(channel, setupCompleter: completer);
@@ -328,8 +327,13 @@ class WatchTogetherPeerService with KeepaliveMixin {
}
}
_reconnectAttempts = 0;
appLogger.d('WatchTogether: Reconnected successfully');
onReconnected?.call();
try {
onReconnected?.call();
} catch (e) {
appLogger.e('WatchTogether: Reconnect callback failed', error: e);
}
} catch (e) {
appLogger.e('WatchTogether: Reconnect failed', error: e);
_handleWebSocketClosed();
@@ -78,14 +78,15 @@ class PerformanceStatsService {
_fpsTrackingActive = true;
if (!_fpsCallbackRegistered) {
_fpsCallbackRegistered = true;
SchedulerBinding.instance.addPersistentFrameCallback(_onFrame);
SchedulerBinding.instance.addTimingsCallback(_onFrameTimings);
}
}
/// Called every frame to count FPS.
void _onFrame(Duration timestamp) {
/// Called with completed frame timings to count FPS without retaining an
/// app-lifetime persistent callback.
void _onFrameTimings(List<FrameTiming> timings) {
if (!_fpsTrackingActive) return;
_frameCount++;
_frameCount += timings.length;
final now = DateTime.now();
final elapsed = now.difference(_lastFpsUpdate);
if (elapsed.inMilliseconds >= 1000) {
@@ -101,6 +102,10 @@ class PerformanceStatsService {
_pollingTimer = null;
_fpsTrackingActive = false;
_currentUiFps = null;
if (_fpsCallbackRegistered) {
SchedulerBinding.instance.removeTimingsCallback(_onFrameTimings);
_fpsCallbackRegistered = false;
}
}
/// Fetch all performance stats from the player.