diff --git a/lib/watch_together/models/sync_message.dart b/lib/watch_together/models/sync_message.dart index d1268e71..421e4894 100644 --- a/lib/watch_together/models/sync_message.dart +++ b/lib/watch_together/models/sync_message.dart @@ -202,7 +202,8 @@ class SyncMessage { timestamp: DateTime.now().millisecondsSinceEpoch, controlMode: controlMode, positionMs: currentPosition.inMilliseconds, - bufferingState: !isPlaying, // Reuse field: false = playing, true = paused + isPlaying: isPlaying, + bufferingState: !isPlaying, // Legacy compat: false = playing, true = paused rate: playbackRate, peerId: peerId, ratingKey: ratingKey, diff --git a/lib/watch_together/models/watch_session.dart b/lib/watch_together/models/watch_session.dart index 72042805..1e5e6025 100644 --- a/lib/watch_together/models/watch_session.dart +++ b/lib/watch_together/models/watch_session.dart @@ -30,10 +30,10 @@ class Participant { final String peerId; final String displayName; final bool isHost; - Duration lastKnownPosition; - bool isBuffering; + final Duration lastKnownPosition; + final bool isBuffering; - Participant({ + const Participant({ required this.peerId, required this.displayName, required this.isHost, diff --git a/lib/watch_together/services/watch_together_sync_manager.dart b/lib/watch_together/services/watch_together_sync_manager.dart index a6a9586c..f4bf9f55 100644 --- a/lib/watch_together/services/watch_together_sync_manager.dart +++ b/lib/watch_together/services/watch_together_sync_manager.dart @@ -62,6 +62,9 @@ class WatchTogetherSyncManager { Timer? _clockSyncTimer; static const Duration _clockSyncInterval = Duration(seconds: 5); + // Timer for clearing sync indicator (prevents flicker from overlapping corrections) + Timer? _syncingTimer; + // Track last known state to avoid duplicate broadcasts bool _lastKnownPlaying = false; double _lastKnownRate = 1.0; @@ -159,6 +162,9 @@ class WatchTogetherSyncManager { _hasAnnouncedReady = false; _deferredPlay = false; _deferredPlayPosition = null; + _firstPlayCompleted = false; + _syncingTimer?.cancel(); + _syncingTimer = null; _clockSyncTimer?.cancel(); _clockSyncTimer = null; _clockOffset = 0; @@ -631,13 +637,15 @@ class WatchTogetherSyncManager { appLogger.w('WatchTogether: Excessive drift (${drift.inSeconds}s), force syncing'); _setSyncing(true); _applyRemoteSeek(estimatedRemoteNow); - Future.delayed(const Duration(milliseconds: 500), () => _setSyncing(false)); + _syncingTimer?.cancel(); + _syncingTimer = Timer(const Duration(milliseconds: 500), () => _setSyncing(false)); } else if (drift > maxAllowedDrift) { // Normal drift correction appLogger.d('WatchTogether: Drift correction (${drift.inMilliseconds}ms)'); _setSyncing(true); _applyRemoteSeek(estimatedRemoteNow); - Future.delayed(const Duration(milliseconds: 300), () => _setSyncing(false)); + _syncingTimer?.cancel(); + _syncingTimer = Timer(const Duration(milliseconds: 300), () => _setSyncing(false)); } } @@ -661,11 +669,6 @@ class WatchTogetherSyncManager { _peerService.sendTo(message.peerId!, SyncMessage.playerReady(peerId: _peerService.myPeerId!, ready: true)); } - // Send host's join info so guest adds host to their participants list - _peerService.sendTo( - message.peerId!, - SyncMessage.join(peerId: _peerService.myPeerId!, displayName: displayName, isHost: true), - ); } } @@ -702,8 +705,9 @@ class WatchTogetherSyncManager { _lastKnownRate = message.rate!; } - // Match play/pause state (bufferingState is reused: false = playing) - if (message.bufferingState == false) { + // Match play/pause state (prefer isPlaying, fall back to legacy bufferingState encoding) + final hostIsPlaying = message.isPlaying ?? (message.bufferingState == false); + if (hostIsPlaying) { // Host was playing — defer until our video is loaded _deferredPlay = true; _deferredPlayPosition = message.position; @@ -775,6 +779,7 @@ class WatchTogetherSyncManager { /// Dispose resources void dispose() { _clockSyncTimer?.cancel(); + _syncingTimer?.cancel(); detachPlayer(); _peerReady.clear(); _hasAnnouncedReady = false;