perf: coalesce watch together rebuilds, static TV overlays

This commit is contained in:
edde746
2026-03-25 06:40:17 +01:00
parent 4beeedd318
commit 4cef99dda2
4 changed files with 80 additions and 42 deletions
+45 -34
View File
@@ -2906,43 +2906,54 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen> with WidgetsBindin
); );
}, },
), ),
// Watch Together: reconnecting to host overlay // Watch Together overlays (isolated from video surface repaints)
Consumer<WatchTogetherProvider>( RepaintBoundary(
builder: (context, provider, child) { child: Stack(
if (!provider.isWaitingForHostReconnect) return const SizedBox.shrink(); children: [
return Positioned( // Watch Together: reconnecting to host overlay
bottom: 120, Selector<WatchTogetherProvider, bool>(
left: 0, selector: (_, provider) => provider.isWaitingForHostReconnect,
right: 0, builder: (context, isWaiting, child) {
child: Center( if (!isWaiting) return const SizedBox.shrink();
child: Container( return Positioned(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), bottom: 120,
decoration: const BoxDecoration( left: 0,
color: Colors.black54, right: 0,
borderRadius: BorderRadius.all(Radius.circular(20)), child: Center(
), child: Container(
child: Row( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
mainAxisSize: MainAxisSize.min, decoration: const BoxDecoration(
children: [ color: Colors.black54,
const SizedBox( borderRadius: BorderRadius.all(Radius.circular(20)),
width: 14, ),
height: 14, child: Row(
child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white), mainAxisSize: MainAxisSize.min,
children: [
if (PlatformDetector.isTV())
const Icon(Symbols.sync_rounded, size: 14, color: Colors.white)
else
const SizedBox(
width: 14,
height: 14,
child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white),
),
const SizedBox(width: 8),
Text(
t.watchTogether.reconnectingToHost,
style: const TextStyle(color: Colors.white, fontSize: 12),
),
],
),
), ),
const SizedBox(width: 8), ),
Text( );
t.watchTogether.reconnectingToHost, },
style: const TextStyle(color: Colors.white, fontSize: 12),
),
],
),
),
), ),
); // Watch Together: participant join/leave notifications
}, const ParticipantNotificationOverlay(),
],
),
), ),
// Watch Together: participant join/leave notifications
const ParticipantNotificationOverlay(),
// Black overlay during exit (no spinner - just covers transparency) // Black overlay during exit (no spinner - just covers transparency)
ValueListenableBuilder<bool>( ValueListenableBuilder<bool>(
valueListenable: _isExiting, valueListenable: _isExiting,
@@ -30,6 +30,21 @@ class WatchTogetherProvider with ChangeNotifier {
String _displayName = 'User'; String _displayName = 'User';
String? _lastHandledCurrentPlaybackKey; String? _lastHandledCurrentPlaybackKey;
// Coalesce rapid-fire notifyListeners() calls into a single rebuild per frame.
// 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;
@override
void notifyListeners() {
if (_notifyScheduled) return;
_notifyScheduled = true;
scheduleMicrotask(() {
_notifyScheduled = false;
super.notifyListeners();
});
}
// Host reconnect grace period // Host reconnect grace period
Timer? _hostReconnectTimer; Timer? _hostReconnectTimer;
bool _isWaitingForHostReconnect = false; bool _isWaitingForHostReconnect = false;
@@ -316,10 +316,11 @@ class WatchTogetherSyncManager {
_clockOffset = 0; _clockOffset = 0;
_pendingPingTimestamp = null; _pendingPingTimestamp = null;
// Initial burst of 3 pings for fast convergence // Initial burst of 2 pings for convergence, with wider spacing to reduce
// main-thread pressure during the join event storm
int burstCount = 0; int burstCount = 0;
Timer.periodic(const Duration(milliseconds: 200), (timer) { Timer.periodic(const Duration(milliseconds: 500), (timer) {
if (burstCount >= 3 || _player == null) { if (burstCount >= 2 || _player == null) {
timer.cancel(); timer.cancel();
return; return;
} }
@@ -7,6 +7,7 @@ import 'package:provider/provider.dart';
import '../../i18n/strings.g.dart'; import '../../i18n/strings.g.dart';
import '../../utils/dialogs.dart'; import '../../utils/dialogs.dart';
import '../../utils/platform_detector.dart';
import '../../utils/snackbar_helper.dart'; import '../../utils/snackbar_helper.dart';
import '../../widgets/overlay_sheet.dart'; import '../../widgets/overlay_sheet.dart';
import '../models/watch_session.dart'; import '../models/watch_session.dart';
@@ -85,10 +86,12 @@ class _SessionIndicator extends StatelessWidget {
children: [ children: [
// Sync indicator or group icon // Sync indicator or group icon
if (isSyncing) if (isSyncing)
const SizedBox( SizedBox(
width: 16, width: 16,
height: 16, height: 16,
child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white), child: PlatformDetector.isTV()
? const Icon(Symbols.sync_rounded, size: 16, color: Colors.white)
: const CircularProgressIndicator(strokeWidth: 2, color: Colors.white),
) )
else else
Icon(Symbols.group, size: 18, color: isHost ? theme.colorScheme.primary : Colors.white), Icon(Symbols.group, size: 18, color: isHost ? theme.colorScheme.primary : Colors.white),
@@ -230,7 +233,13 @@ class _SessionMenuSheet extends StatelessWidget {
title: Text(p.displayName), title: Text(p.displayName),
subtitle: p.isHost ? Text(t.watchTogether.host) : null, subtitle: p.isHost ? Text(t.watchTogether.host) : null,
trailing: p.isBuffering trailing: p.isBuffering
? const SizedBox(width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2)) ? SizedBox(
width: 16,
height: 16,
child: PlatformDetector.isTV()
? const Icon(Symbols.hourglass_empty_rounded, size: 16)
: const CircularProgressIndicator(strokeWidth: 2),
)
: null, : null,
dense: true, dense: true,
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
@@ -388,10 +397,12 @@ class SyncingIndicator extends StatelessWidget {
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
const SizedBox( SizedBox(
width: 14, width: 14,
height: 14, height: 14,
child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white), child: PlatformDetector.isTV()
? const Icon(Symbols.sync_rounded, size: 14, color: Colors.white)
: const CircularProgressIndicator(strokeWidth: 2, color: Colors.white),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
Text(t.watchTogether.syncing, style: const TextStyle(color: Colors.white, fontSize: 12)), Text(t.watchTogether.syncing, style: const TextStyle(color: Colors.white, fontSize: 12)),