From 3d30ea13428da1e3eee77f9ed8403198478ebfde Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Thu, 12 Mar 2026 23:22:58 +0100 Subject: [PATCH] fix(macos): reduce idle CPU when video player is paused - Remove vulkan-swap-mode mailbox (continuous GPU rendering even when paused) - End playback activity assertion on pause, restore on play - Fix PerformanceStatsService persistent frame callback leak --- .../performance_stats_service.dart | 11 ++++++++++- macos/Runner/MpvPlayer/MpvPlayerCore.swift | 14 +++++++++++++- macos/Runner/MpvPlayer/MpvPlayerPlugin.swift | 1 + 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/lib/widgets/video_controls/widgets/performance_overlay/performance_stats_service.dart b/lib/widgets/video_controls/widgets/performance_overlay/performance_stats_service.dart index 42151856..ada0f434 100644 --- a/lib/widgets/video_controls/widgets/performance_overlay/performance_stats_service.dart +++ b/lib/widgets/video_controls/widgets/performance_overlay/performance_stats_service.dart @@ -33,6 +33,8 @@ class PerformanceStatsService { int _frameCount = 0; DateTime _lastFpsUpdate = DateTime.now(); double? _currentUiFps; + bool _fpsTrackingActive = false; + bool _fpsCallbackRegistered = false; // Track runtime player type for logging (can differ from Dart object type after fallback) // Values: 'exoplayer', 'mpv', or 'unknown' @@ -77,11 +79,16 @@ class PerformanceStatsService { void _startFpsTracking() { _frameCount = 0; _lastFpsUpdate = DateTime.now(); - SchedulerBinding.instance.addPersistentFrameCallback(_onFrame); + _fpsTrackingActive = true; + if (!_fpsCallbackRegistered) { + _fpsCallbackRegistered = true; + SchedulerBinding.instance.addPersistentFrameCallback(_onFrame); + } } /// Called every frame to count FPS. void _onFrame(Duration timestamp) { + if (!_fpsTrackingActive) return; _frameCount++; final now = DateTime.now(); final elapsed = now.difference(_lastFpsUpdate); @@ -96,6 +103,8 @@ class PerformanceStatsService { void stopPolling() { _pollingTimer?.cancel(); _pollingTimer = null; + _fpsTrackingActive = false; + _currentUiFps = null; } /// Fetch all performance stats from the player. diff --git a/macos/Runner/MpvPlayer/MpvPlayerCore.swift b/macos/Runner/MpvPlayer/MpvPlayerCore.swift index fd806194..8daa89fc 100644 --- a/macos/Runner/MpvPlayer/MpvPlayerCore.swift +++ b/macos/Runner/MpvPlayer/MpvPlayerCore.swift @@ -85,7 +85,7 @@ class MpvPlayerCore: MpvPlayerCoreBase { override func configurePlatformMpvOptions() { guard let mpv else { return } checkError(mpv_set_option_string(mpv, "ao", "avfoundation,coreaudio")) - checkError(mpv_set_option_string(mpv, "vulkan-swap-mode", "mailbox")) + // Default fifo (vsync) mode — mailbox was causing continuous GPU rendering even when paused } var videoLayer: CAMetalLayer? { metalLayer } @@ -113,9 +113,13 @@ class MpvPlayerCore: MpvPlayerCoreBase { command(["seek", "0", "relative+exact"]) } + private var isVisible = false + func setVisible(_ visible: Bool) { guard let metalLayer, !isPipActive else { return } + isVisible = visible + if visible { metalLayer.removeFromSuperlayer() if let superlayer = window?.contentView?.layer { @@ -130,6 +134,14 @@ class MpvPlayerCore: MpvPlayerCoreBase { print("[MpvPlayerCore] setVisible(\(visible))") } + func setPaused(_ paused: Bool) { + if paused { + endPlaybackActivity() + } else if isVisible { + beginPlaybackActivity() + } + } + func updateFrame(_ frame: CGRect? = nil) { guard let metalLayer, !isPipActive else { return } diff --git a/macos/Runner/MpvPlayer/MpvPlayerPlugin.swift b/macos/Runner/MpvPlayer/MpvPlayerPlugin.swift index 599f9d04..8606274c 100644 --- a/macos/Runner/MpvPlayer/MpvPlayerPlugin.swift +++ b/macos/Runner/MpvPlayer/MpvPlayerPlugin.swift @@ -277,6 +277,7 @@ class MpvPlayerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, MpvPluginS if name == "pause" { let isPlaying = value == "no" pipController?.setPlaying(isPlaying) + playerCore?.setPaused(!isPlaying) } result(nil)