fix(watch-together): restore host resume

close #1052
This commit is contained in:
edde746
2026-05-15 18:33:02 +02:00
parent e54bbb2214
commit 562d19c207
2 changed files with 75 additions and 6 deletions
@@ -894,7 +894,7 @@ class WatchTogetherSyncManager {
onSessionConfigReceived?.call(message.controlMode!);
}
await _runGuardedRemoteAction(
final applied = await _runGuardedRemoteAction(
actionName: 'session config',
expectedAttachmentGeneration: expectedAttachmentGeneration,
action: (player, attachmentGeneration) async {
@@ -956,6 +956,10 @@ class WatchTogetherSyncManager {
return true;
},
);
if (applied) {
_reannounceReady(reason: 'session config');
}
}
/// Set syncing state and notify listeners
@@ -984,6 +988,14 @@ class WatchTogetherSyncManager {
_backgrounded = value;
}
void _reannounceReady({required String reason}) {
if (_hasAnnouncedReady && _peerService.myPeerId != null) {
_peerReady[_peerService.myPeerId!] = true;
_peerService.broadcast(SyncMessage.playerReady(peerId: _peerService.myPeerId!, ready: true));
appLogger.d('WatchTogether: Re-announced player ready after $reason');
}
}
/// Re-announce player readiness after reconnect.
///
/// During reconnect the host resets our _peerReady entry to false via
@@ -991,11 +1003,7 @@ class WatchTogetherSyncManager {
/// reset because the player stays attached). Re-broadcast so the host
/// doesn't stay stuck in the deferred-play gate.
void reannounceReadyIfNeeded() {
if (_hasAnnouncedReady && _peerService.myPeerId != null) {
_peerReady[_peerService.myPeerId!] = true;
_peerService.broadcast(SyncMessage.playerReady(peerId: _peerService.myPeerId!, ready: true));
appLogger.d('WatchTogether: Re-announced player ready after reconnect');
}
_reannounceReady(reason: 'reconnect');
}
/// Send join announcement to all peers
@@ -58,6 +58,67 @@ void main() {
await peerService.close();
});
test('ready guest re-announces readiness after receiving session config', () async {
final peerService = _FakeWatchTogetherPeerService(peerId: 'guest');
final player = _FakePlayer(playing: false, position: const Duration(seconds: 10));
final manager = _guestManager(peerService, controlMode: ControlMode.anyone);
manager.initializeParticipants(['guest', 'host']);
manager.attachPlayer(player);
peerService.broadcasts.clear();
peerService.emit(
SyncMessage.sessionConfig(
controlMode: ControlMode.anyone,
currentPosition: const Duration(seconds: 20),
isPlaying: false,
playbackRate: 1.0,
peerId: 'host',
),
);
await _settle();
expect(
peerService.broadcasts.where(
(message) =>
message.type == SyncMessageType.playerReady &&
message.peerId == 'guest' &&
message.bufferingState == true,
),
isNotEmpty,
);
expect(player.state.position, const Duration(seconds: 20));
expect(player.state.playing, isFalse);
manager.dispose();
await player.dispose();
await peerService.close();
});
test('host local play is not deferred after guest readiness is restored', () async {
final peerService = _FakeWatchTogetherPeerService(peerId: 'host');
final player = _FakePlayer(playing: false, position: const Duration(minutes: 3));
final manager = _hostManager(peerService);
final deferredStates = <bool>[];
manager.onDeferredPlayChanged = deferredStates.add;
manager.initializeParticipants(['host', 'guest']);
manager.attachPlayer(player);
peerService.emit(SyncMessage.playerReady(peerId: 'guest', ready: true));
await _settle();
peerService.broadcasts.clear();
await player.emitPlaying(true);
expect(deferredStates, isNot(contains(true)));
expect(player.state.playing, isTrue);
expect(peerService.broadcasts.where((m) => m.type == SyncMessageType.play), isNotEmpty);
manager.dispose();
await player.dispose();
await peerService.close();
});
test('removing a disconnected not-ready peer resumes deferred play', () async {
final peerService = _FakeWatchTogetherPeerService(peerId: 'host');
final player = _FakePlayer(playing: false, position: const Duration(minutes: 5));