perf: watch together CPU overhead

This commit is contained in:
edde746
2026-03-08 16:57:15 +01:00
parent 9e2e1f72ce
commit 16676bca49
5 changed files with 44 additions and 63 deletions
@@ -178,7 +178,6 @@ class WatchTogetherPeerService with KeepaliveMixin {
try {
final payloadStr = payload is String ? payload : jsonEncode(payload);
final syncMsg = SyncMessage.fromJson(payloadStr);
appLogger.d('WatchTogether: Received ${syncMsg.type} from $from');
_safeAdd(_messageReceivedController, syncMsg);
} catch (e) {
appLogger.e('WatchTogether: Failed to parse sync message payload', error: e);
@@ -369,14 +368,12 @@ class WatchTogetherPeerService with KeepaliveMixin {
/// Broadcast a message to all connected peers
void broadcast(SyncMessage message) {
final payload = message.toJson();
appLogger.d('WatchTogether: Broadcasting ${message.type} to ${_connectedPeers.length} peers');
_sendRaw({'type': 'broadcast', 'payload': payload});
}
/// Send a message to a specific peer
void sendTo(String peerId, SyncMessage message) {
final payload = message.toJson();
appLogger.d('WatchTogether: Sending ${message.type} to $peerId');
_sendRaw({'type': 'sendTo', 'to': peerId, 'payload': payload});
}
@@ -66,6 +66,9 @@ class WatchTogetherSyncManager {
// Timer for clearing sync indicator (prevents flicker from overlapping corrections)
Timer? _syncingTimer;
// Debounce timer for buffering broadcasts
Timer? _bufferingDebounceTimer;
// Track last known state to avoid duplicate broadcasts
bool _lastKnownPlaying = false;
double _lastKnownRate = 1.0;
@@ -87,7 +90,6 @@ class WatchTogetherSyncManager {
/// Update the session (e.g., when control mode changes)
void updateSession(WatchSession session) {
_session = session;
appLogger.d('WatchTogether: Sync manager session updated, controlMode: ${session.controlMode}');
}
/// Whether this manager has a player attached
@@ -172,6 +174,8 @@ class WatchTogetherSyncManager {
_firstPlayCompleted = false;
_syncingTimer?.cancel();
_syncingTimer = null;
_bufferingDebounceTimer?.cancel();
_bufferingDebounceTimer = null;
_clockSyncTimer?.cancel();
_clockSyncTimer = null;
_clockOffset = 0;
@@ -246,8 +250,11 @@ class WatchTogetherSyncManager {
}
}
// Broadcast for UI (peer buffering indicators) — no playback control
_peerService.broadcast(SyncMessage.buffering(isBuffering, peerId: _peerService.myPeerId));
// Broadcast for UI (peer buffering indicators) — debounced to avoid churn
_bufferingDebounceTimer?.cancel();
_bufferingDebounceTimer = Timer(const Duration(milliseconds: 300), () {
_peerService.broadcast(SyncMessage.buffering(isBuffering, peerId: _peerService.myPeerId));
});
}),
);
@@ -361,7 +368,6 @@ class WatchTogetherSyncManager {
// Exponential moving average
const alpha = 0.3;
_clockOffset = (_clockOffset + (alpha * (sampleOffset - _clockOffset)).round());
appLogger.d('WatchTogether: Clock offset updated: ${_clockOffset}ms (sample: ${sampleOffset}ms, RTT: ${rtt}ms)');
}
}
@@ -504,10 +510,7 @@ class WatchTogetherSyncManager {
/// Broadcast play/pause state
void _broadcastPlayPause(bool isPlaying) {
if (!_canControl()) {
appLogger.d('WatchTogether: Cannot control playback in hostOnly mode');
return;
}
if (!_canControl()) return;
if (isPlaying) {
final position = _player?.state.position ?? Duration.zero;
@@ -519,10 +522,7 @@ class WatchTogetherSyncManager {
/// Called when user seeks locally
void onLocalSeek(Duration position) {
if (!_canControl()) {
appLogger.d('WatchTogether: Cannot control playback in hostOnly mode');
return;
}
if (!_canControl()) return;
_peerService.broadcast(SyncMessage.seek(position, peerId: _peerService.myPeerId));
}
@@ -544,7 +544,6 @@ class WatchTogetherSyncManager {
message.type == SyncMessageType.rate;
if (isControlMessage) {
appLogger.d('WatchTogether: Host relaying ${message.type} from ${message.peerId}');
_peerService.broadcast(message);
}
}
@@ -561,35 +560,23 @@ class WatchTogetherSyncManager {
message.type == SyncMessageType.pong ||
message.type == SyncMessageType.mediaSwitch;
if (!isHostMessage && !isMetaMessage) {
appLogger.d('WatchTogether: Ignoring non-host message in hostOnly mode');
return;
}
if (!isHostMessage && !isMetaMessage) return;
}
switch (message.type) {
case SyncMessageType.play:
if (!_shouldApplyRemoteControl(message)) {
appLogger.d('WatchTogether: Ignoring play from non-host in hostOnly mode');
break;
}
if (!_shouldApplyRemoteControl(message)) break;
await _applyRemotePlay(position: message.position, expectedAttachmentGeneration: queuedAttachmentGeneration);
break;
case SyncMessageType.pause:
if (!_shouldApplyRemoteControl(message)) {
appLogger.d('WatchTogether: Ignoring pause from non-host in hostOnly mode');
break;
}
if (!_shouldApplyRemoteControl(message)) break;
_deferredPlay = false;
await _applyRemotePause(expectedAttachmentGeneration: queuedAttachmentGeneration);
break;
case SyncMessageType.seek:
if (!_shouldApplyRemoteControl(message)) {
appLogger.d('WatchTogether: Ignoring seek from non-host in hostOnly mode');
break;
}
if (!_shouldApplyRemoteControl(message)) break;
if (message.position != null) {
await _applyRemoteSeek(message.position!, expectedAttachmentGeneration: queuedAttachmentGeneration);
}
@@ -613,23 +600,18 @@ class WatchTogetherSyncManager {
final localPlaying = player.state.playing;
if (message.isPlaying! && !localPlaying) {
appLogger.d('WatchTogether: Play/pause state diverged, syncing to host (playing)');
await _applyRemotePlay(
position: message.position,
expectedAttachmentGeneration: queuedAttachmentGeneration,
);
} else if (!message.isPlaying! && localPlaying) {
appLogger.d('WatchTogether: Play/pause state diverged, syncing to host (paused)');
await _applyRemotePause(expectedAttachmentGeneration: queuedAttachmentGeneration);
}
}
break;
case SyncMessageType.rate:
if (!_shouldApplyRemoteControl(message)) {
appLogger.d('WatchTogether: Ignoring rate from non-host in hostOnly mode');
break;
}
if (!_shouldApplyRemoteControl(message)) break;
if (message.rate != null) {
await _applyRemoteRate(message.rate!, expectedAttachmentGeneration: queuedAttachmentGeneration);
}
@@ -703,7 +685,6 @@ class WatchTogetherSyncManager {
/// Apply remote play command
Future<bool> _applyRemotePlay({Duration? position, int? expectedAttachmentGeneration}) async {
appLogger.d('WatchTogether: Applying remote PLAY${position != null ? ' at ${position.inSeconds}s' : ''}');
return _runGuardedRemoteAction(
actionName: 'play',
expectedAttachmentGeneration: expectedAttachmentGeneration,
@@ -734,7 +715,6 @@ class WatchTogetherSyncManager {
/// Apply remote pause command
Future<bool> _applyRemotePause({int? expectedAttachmentGeneration}) async {
appLogger.d('WatchTogether: Applying remote PAUSE');
return _runGuardedRemoteAction(
actionName: 'pause',
expectedAttachmentGeneration: expectedAttachmentGeneration,
@@ -755,7 +735,6 @@ class WatchTogetherSyncManager {
/// Apply remote seek command
Future<bool> _applyRemoteSeek(Duration position, {int? expectedAttachmentGeneration}) async {
appLogger.d('WatchTogether: Applying remote SEEK to ${position.inSeconds}s');
return _runGuardedRemoteAction(
actionName: 'seek',
expectedAttachmentGeneration: expectedAttachmentGeneration,
@@ -772,7 +751,6 @@ class WatchTogetherSyncManager {
/// Apply remote rate change
Future<bool> _applyRemoteRate(double rate, {int? expectedAttachmentGeneration}) async {
appLogger.d('WatchTogether: Applying remote RATE: $rate');
return _runGuardedRemoteAction(
actionName: 'rate',
expectedAttachmentGeneration: expectedAttachmentGeneration,
@@ -838,8 +816,6 @@ class WatchTogetherSyncManager {
_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);
final didSeek = await _applyRemoteSeek(
estimatedRemoteNow,