fix: watch together guest losing sync when host switches episodes
close #431
This commit is contained in:
@@ -45,6 +45,9 @@ enum SyncMessageType {
|
||||
|
||||
/// Player is ready (attached and loaded)
|
||||
playerReady,
|
||||
|
||||
/// Request session config from host (guest recovery)
|
||||
requestSessionConfig,
|
||||
}
|
||||
|
||||
/// A message sent over the WebRTC data channel for synchronization
|
||||
@@ -181,12 +184,18 @@ class SyncMessage {
|
||||
}
|
||||
|
||||
/// Create a SESSION_CONFIG message (sent by host to new guests)
|
||||
///
|
||||
/// Optionally includes current media info so guests can catch up
|
||||
/// if they missed a mediaSwitch broadcast.
|
||||
factory SyncMessage.sessionConfig({
|
||||
required ControlMode controlMode,
|
||||
required Duration currentPosition,
|
||||
required bool isPlaying,
|
||||
required double playbackRate,
|
||||
String? peerId,
|
||||
String? ratingKey,
|
||||
String? serverId,
|
||||
String? mediaTitle,
|
||||
}) {
|
||||
return SyncMessage(
|
||||
type: SyncMessageType.sessionConfig,
|
||||
@@ -196,6 +205,18 @@ class SyncMessage {
|
||||
bufferingState: !isPlaying, // Reuse field: false = playing, true = paused
|
||||
rate: playbackRate,
|
||||
peerId: peerId,
|
||||
ratingKey: ratingKey,
|
||||
serverId: serverId,
|
||||
mediaTitle: mediaTitle,
|
||||
);
|
||||
}
|
||||
|
||||
/// Create a REQUEST_SESSION_CONFIG message (sent by guest to request current config from host)
|
||||
factory SyncMessage.requestSessionConfig({String? peerId}) {
|
||||
return SyncMessage(
|
||||
type: SyncMessageType.requestSessionConfig,
|
||||
timestamp: DateTime.now().millisecondsSinceEpoch,
|
||||
peerId: peerId,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -356,6 +356,10 @@ class WatchTogetherProvider with ChangeNotifier {
|
||||
_handleSessionConfig(message);
|
||||
break;
|
||||
|
||||
case SyncMessageType.requestSessionConfig:
|
||||
// Handled at sync manager level (host responds with config)
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -372,6 +376,18 @@ class WatchTogetherProvider with ChangeNotifier {
|
||||
_syncManager?.updateSession(_session!); // Update sync manager if it exists
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// If config contains media info that differs from our current state,
|
||||
// trigger a media switch so the guest navigates to the correct content.
|
||||
// This handles the case where a guest missed a mediaSwitch broadcast
|
||||
// (e.g., host switched episodes while guest was popping out of the player).
|
||||
if (message.ratingKey != null &&
|
||||
message.serverId != null &&
|
||||
message.mediaTitle != null &&
|
||||
message.ratingKey != _session?.mediaRatingKey) {
|
||||
appLogger.d('WatchTogether: Session config contains different media, triggering switch');
|
||||
_handleMediaSwitch(message);
|
||||
}
|
||||
}
|
||||
|
||||
/// Called when user seeks locally (to broadcast to peers)
|
||||
@@ -431,7 +447,17 @@ class WatchTogetherProvider with ChangeNotifier {
|
||||
|
||||
appLogger.d('WatchTogether: Received media switch: ${message.mediaTitle}');
|
||||
|
||||
// Update local session state
|
||||
// Dispatch callback BEFORE updating session state.
|
||||
// If the callback fails silently (e.g., player disposed mid-animation),
|
||||
// the ratingKey stays unchanged so the next positionSync or re-sent
|
||||
// message can retry instead of being treated as a duplicate.
|
||||
if (onPlayerMediaSwitched != null) {
|
||||
onPlayerMediaSwitched!(message.ratingKey!, message.serverId!, message.mediaTitle!);
|
||||
} else {
|
||||
onMediaSwitched?.call(message.ratingKey!, message.serverId!, message.mediaTitle!);
|
||||
}
|
||||
|
||||
// Update local session state after successful dispatch
|
||||
_session = _session?.copyWith(
|
||||
mediaRatingKey: message.ratingKey,
|
||||
mediaServerId: message.serverId,
|
||||
@@ -439,15 +465,6 @@ class WatchTogetherProvider with ChangeNotifier {
|
||||
);
|
||||
|
||||
notifyListeners();
|
||||
|
||||
// If player handler is set (VideoPlayerScreen is active), use that for proper navigation context
|
||||
if (onPlayerMediaSwitched != null) {
|
||||
onPlayerMediaSwitched!(message.ratingKey!, message.serverId!, message.mediaTitle!);
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, trigger app-level navigation callback (MainScreen handles it)
|
||||
onMediaSwitched?.call(message.ratingKey!, message.serverId!, message.mediaTitle!);
|
||||
}
|
||||
|
||||
/// Notify guests that host is exiting the video player
|
||||
@@ -469,6 +486,11 @@ class WatchTogetherProvider with ChangeNotifier {
|
||||
|
||||
appLogger.d('WatchTogether: Host exited player, callback set: ${onHostExitedPlayer != null}');
|
||||
|
||||
// Clear the player callback BEFORE popping so that any mediaSwitch message
|
||||
// arriving during the pop animation routes to MainScreen's handler instead
|
||||
// of the dying VideoPlayerScreen.
|
||||
onPlayerMediaSwitched = null;
|
||||
|
||||
// Trigger callback for the app to navigate guest out of player
|
||||
if (onHostExitedPlayer != null) {
|
||||
onHostExitedPlayer!.call();
|
||||
|
||||
@@ -136,6 +136,15 @@ class WatchTogetherSyncManager {
|
||||
|
||||
// Note: playerReady will be announced when video loads (first buffering: false)
|
||||
|
||||
// If guest, request current session config from host in case we missed
|
||||
// a mediaSwitch broadcast (e.g., host switched episodes while we were
|
||||
// popping out of the previous player).
|
||||
if (!_session.isHost) {
|
||||
_peerService.broadcast(
|
||||
SyncMessage.requestSessionConfig(peerId: _peerService.myPeerId),
|
||||
);
|
||||
}
|
||||
|
||||
appLogger.d('WatchTogether: Player attached, isHost: ${_session.isHost}');
|
||||
}
|
||||
|
||||
@@ -504,6 +513,14 @@ class WatchTogetherSyncManager {
|
||||
await _checkAutoResume();
|
||||
}
|
||||
break;
|
||||
|
||||
case SyncMessageType.requestSessionConfig:
|
||||
// Guest is requesting current session config (recovery after missed mediaSwitch)
|
||||
if (_session.isHost && _hasAnnouncedReady && message.peerId != null) {
|
||||
appLogger.d('WatchTogether: Guest ${message.peerId} requested session config, sending');
|
||||
_sendSessionConfig(toPeerId: message.peerId);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -776,6 +793,9 @@ class WatchTogetherSyncManager {
|
||||
isPlaying: isPlaying,
|
||||
playbackRate: rate,
|
||||
peerId: _peerService.myPeerId,
|
||||
ratingKey: _session.mediaRatingKey,
|
||||
serverId: _session.mediaServerId,
|
||||
mediaTitle: _session.mediaTitle,
|
||||
);
|
||||
|
||||
if (toPeerId != null) {
|
||||
|
||||
Reference in New Issue
Block a user