fix: close async lifecycle races
This commit is contained in:
@@ -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();
|
||||
|
||||
+9
-4
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user