Files
plezy/lib/widgets/video_controls/widgets/linux_keep_alive.dart
T
edde746 644c782edd fix(player): tick the frame-clock keep-alive only on Linux
The 1x1 keep-alive repaint loop was extended to Windows in a87aa296 to
paper over the legacy compositing path's resize desync (#227); the DComp
rework replaced that presentation path entirely. On the DComp engine the
100ms repaints become DirectComposition commits during playback, and
once fullscreen focus engages VRR (FreeSync/G-Sync) every commit forces
a scanout off the video's cadence - the micro-stutter of #1707.

The widget now owns the platform decision behind a test seam, and a new
quiescence test pins the hidden-chrome player UI to zero scheduled
frames so no future ticker can silently reintroduce the defect.
2026-07-29 19:04:57 +02:00

62 lines
2.0 KiB
Dart

import 'dart:async' show Timer;
import 'dart:io' show Platform;
import 'package:flutter/material.dart';
/// A 1x1 pixel widget that continuously repaints to keep Flutter's frame clock
/// active on Linux, where GTK's frame clock goes idle and freezes animations.
///
/// Linux-only by design. Mounting this on other platforms is harmful: on
/// Windows the 10Hz repaints become DirectComposition commits during playback,
/// and once fullscreen focus engages VRR (FreeSync/G-Sync) each commit forces a
/// scanout off the video's cadence — the micro-stutter of issue #1707. Every
/// non-Linux platform must instead uphold the invariant that the player UI
/// schedules no frames while its chrome is hidden.
class LinuxKeepAlive extends StatefulWidget {
const LinuxKeepAlive({super.key});
/// Forces the platform decision so tests can exercise both the ticking and
/// the inert path regardless of host OS.
@visibleForTesting
static bool? debugIsLinuxOverride;
/// Whether this widget repaints on the current platform. Exposed so the
/// quiescence test can pin the policy to exactly [Platform.isLinux].
@visibleForTesting
static bool get ticksOnThisPlatform => debugIsLinuxOverride ?? Platform.isLinux;
@override
State<LinuxKeepAlive> createState() => _LinuxKeepAliveState();
}
class _LinuxKeepAliveState extends State<LinuxKeepAlive> {
Timer? _timer;
int _tick = 0;
@override
void initState() {
super.initState();
if (!LinuxKeepAlive.ticksOnThisPlatform) return;
// Repaint every 100ms to keep Flutter's frame scheduler active.
_timer = Timer.periodic(const Duration(milliseconds: 100), (_) {
if (mounted) {
setState(() {
_tick++;
});
}
});
}
@override
void dispose() {
_timer?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
if (_timer == null) return const SizedBox.shrink();
return SizedBox(width: 1, height: 1, child: ColoredBox(color: Color.fromRGBO(0, 0, 0, _tick % 2 == 0 ? 0.1 : 0.2)));
}
}