fix: watch together bugs and server write serialization
This commit is contained in:
@@ -36,20 +36,22 @@ class WatchTogetherProvider with ChangeNotifier {
|
||||
// During Watch Together join, 4-5 notifications fire within milliseconds;
|
||||
// this batches them into one rebuild to avoid overwhelming low-end devices.
|
||||
bool _notifyScheduled = false;
|
||||
bool _disposed = false;
|
||||
|
||||
@override
|
||||
void notifyListeners() {
|
||||
if (_notifyScheduled) return;
|
||||
if (_disposed || _notifyScheduled) return;
|
||||
_notifyScheduled = true;
|
||||
scheduleMicrotask(() {
|
||||
_notifyScheduled = false;
|
||||
super.notifyListeners();
|
||||
if (!_disposed) super.notifyListeners();
|
||||
});
|
||||
}
|
||||
|
||||
// Host reconnect grace period
|
||||
Timer? _hostReconnectTimer;
|
||||
bool _isWaitingForHostReconnect = false;
|
||||
bool _hostIntentionallyLeft = false;
|
||||
|
||||
// Debounce map for action events (peerId+type → last emission timestamp)
|
||||
final Map<String, int> _lastActionEventMs = {};
|
||||
@@ -131,7 +133,6 @@ class WatchTogetherProvider with ChangeNotifier {
|
||||
role: session.role,
|
||||
controlMode: session.controlMode,
|
||||
state: session.state,
|
||||
participants: session.participants,
|
||||
errorMessage: session.errorMessage,
|
||||
hostPeerId: session.hostPeerId,
|
||||
);
|
||||
@@ -174,6 +175,13 @@ class WatchTogetherProvider with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
/// Wire up reconnection handler to re-announce join after reconnect
|
||||
void _wireReconnectHandler() {
|
||||
_peerService!.onReconnected = () {
|
||||
_syncManager?.announceJoin(_displayName);
|
||||
};
|
||||
}
|
||||
|
||||
/// Wire up sync manager's state change callback to update provider state
|
||||
void _wireSyncStateChanges() {
|
||||
_syncManager!.onSyncStateChanged = (isSyncing) {
|
||||
@@ -227,6 +235,7 @@ class WatchTogetherProvider with ChangeNotifier {
|
||||
);
|
||||
|
||||
_wireSyncStateChanges();
|
||||
_wireReconnectHandler();
|
||||
|
||||
notifyListeners();
|
||||
appLogger.d('WatchTogether: Session created: $createdSessionId');
|
||||
@@ -276,6 +285,7 @@ class WatchTogetherProvider with ChangeNotifier {
|
||||
};
|
||||
|
||||
_wireSyncStateChanges();
|
||||
_wireReconnectHandler();
|
||||
|
||||
// Add self to participants
|
||||
_participants.add(Participant(peerId: _peerService!.myPeerId!, displayName: _displayName, isHost: false));
|
||||
@@ -298,25 +308,15 @@ class WatchTogetherProvider with ChangeNotifier {
|
||||
///
|
||||
/// Returns `true` if the user became the host.
|
||||
Future<bool> enterRoom(String sessionId, {ControlMode controlMode = ControlMode.anyone, String? displayName}) async {
|
||||
// Probe the relay to determine if the room exists, then delegate to
|
||||
// the existing createSession / joinSession which handle all setup.
|
||||
final customRelayUrl = SettingsService.instanceOrNull?.getCustomRelayUrl();
|
||||
final probe = WatchTogetherPeerService(customBaseUrl: customRelayUrl);
|
||||
try {
|
||||
final becameHost = await probe.joinOrCreateSession(sessionId);
|
||||
final shouldBeHost = becameHost || probe.connectedPeers.isEmpty;
|
||||
await probe.disconnect();
|
||||
probe.dispose();
|
||||
|
||||
if (shouldBeHost) {
|
||||
await joinSession(sessionId, displayName: displayName);
|
||||
return false; // joined as guest
|
||||
} on PeerError catch (e) {
|
||||
// Room doesn't exist — create it as host
|
||||
if (e.serverCode == 'room_not_found') {
|
||||
await createSession(controlMode: controlMode, displayName: displayName, sessionId: sessionId);
|
||||
} else {
|
||||
await joinSession(sessionId, displayName: displayName);
|
||||
return true;
|
||||
}
|
||||
return shouldBeHost;
|
||||
} catch (e) {
|
||||
await probe.disconnect();
|
||||
probe.dispose();
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
@@ -358,6 +358,7 @@ class WatchTogetherProvider with ChangeNotifier {
|
||||
_isDeferredPlay = false;
|
||||
_lastHandledCurrentPlaybackKey = null;
|
||||
_lastActionEventMs.clear();
|
||||
_hostIntentionallyLeft = false;
|
||||
|
||||
notifyListeners();
|
||||
appLogger.d('WatchTogether: Session left');
|
||||
@@ -410,8 +411,9 @@ class WatchTogetherProvider with ChangeNotifier {
|
||||
|
||||
_participants.removeWhere((p) => p.peerId == peerId);
|
||||
|
||||
// If host disconnected, start grace period for reconnection
|
||||
if (!isHost && peerId == _session?.hostPeerId) {
|
||||
// If host disconnected unexpectedly, start grace period for reconnection.
|
||||
// Skip if the host already sent a deliberate leave message.
|
||||
if (!isHost && peerId == _session?.hostPeerId && !_hostIntentionallyLeft) {
|
||||
_startHostReconnectGracePeriod();
|
||||
} else if (disconnectedName != null) {
|
||||
_participantEventController.add(
|
||||
@@ -459,16 +461,16 @@ class WatchTogetherProvider with ChangeNotifier {
|
||||
_participantEventController.add(
|
||||
ParticipantEvent(displayName: message.displayName!, type: ParticipantEventType.joined),
|
||||
);
|
||||
}
|
||||
|
||||
// Send our join info back so the new peer adds us to their
|
||||
// participant list. Every peer does this (not just the host)
|
||||
// so that late joiners learn about all existing participants.
|
||||
if (_peerService != null) {
|
||||
_peerService!.sendTo(
|
||||
message.peerId!,
|
||||
SyncMessage.join(peerId: _peerService!.myPeerId!, displayName: _displayName, isHost: isHost),
|
||||
);
|
||||
// Send our join info back so the new peer adds us to their
|
||||
// participant list. Only reply to NEW peers to avoid an
|
||||
// infinite join ping-pong (A→join→B→join→A→...).
|
||||
if (_peerService != null) {
|
||||
_peerService!.sendTo(
|
||||
message.peerId!,
|
||||
SyncMessage.join(peerId: _peerService!.myPeerId!, displayName: _displayName, isHost: isHost),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
@@ -487,6 +489,14 @@ class WatchTogetherProvider with ChangeNotifier {
|
||||
ParticipantEvent(displayName: leavingName, type: ParticipantEventType.left),
|
||||
);
|
||||
}
|
||||
|
||||
// If the host deliberately left, end the session immediately
|
||||
// instead of waiting for the 15s reconnect grace period.
|
||||
if (!isHost && message.peerId == _session?.hostPeerId) {
|
||||
_hostIntentionallyLeft = true;
|
||||
_handleHostExitedPlayer(message);
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
break;
|
||||
@@ -739,6 +749,7 @@ class WatchTogetherProvider with ChangeNotifier {
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_disposed = true;
|
||||
_cancelHostReconnectGracePeriod();
|
||||
_participantEventController.close();
|
||||
leaveSession();
|
||||
|
||||
Reference in New Issue
Block a user