import 'dart:io'; import 'package:flutter/services.dart'; import 'fullscreen_state_manager.dart'; import 'settings_service.dart'; import '../utils/app_logger.dart'; /// Orchestrates Windows display mode matching (refresh rate, HDR) during video playback. /// Uses the same platform channel as the mpv player (com.plezy/mpv_player). class DisplayModeService { static const _channel = MethodChannel('com.plezy/mpv_player'); final SettingsService _settings; final FullscreenStateManager _fullscreen; bool _displayModeChanged = false; bool _hdrStateChanged = false; bool get hdrStateChanged => _hdrStateChanged; bool get anyChangeApplied => _displayModeChanged || _hdrStateChanged; DisplayModeService(this._settings, this._fullscreen); /// Apply display matching based on video properties. Returns the delay /// duration to wait before starting playback. Future applyDisplayMatching({required double? fps, required double? sigPeak}) async { if (!Platform.isWindows) return Duration.zero; if (!_fullscreen.isFullscreen) { appLogger.d('Display matching skipped: not in fullscreen'); return Duration.zero; } bool anyChange = false; // Refresh rate matching. if (_settings.getMatchRefreshRate() && fps != null && fps > 0) { try { final success = await _matchRefreshRate(fps); anyChange |= success; } catch (e) { appLogger.w('Failed to match refresh rate', error: e); } } // Dynamic range matching. if (_settings.getMatchDynamicRange() && sigPeak != null && sigPeak > 1.0) { try { final success = await _enableSystemHDR(); anyChange |= success; } catch (e) { appLogger.w('Failed to enable system HDR', error: e); } } if (anyChange) { final delaySec = _settings.getDisplaySwitchDelay(); return Duration(seconds: delaySec); } return Duration.zero; } /// Restore all display settings to their original state. Future restoreAll() async { if (!Platform.isWindows) return; if (_hdrStateChanged) { try { await _channel.invokeMethod('restoreSystemHDR'); _hdrStateChanged = false; appLogger.d('Restored system HDR state'); } catch (e) { appLogger.w('Failed to restore system HDR', error: e); } } if (_displayModeChanged) { try { await _channel.invokeMethod('restoreDisplayMode'); _displayModeChanged = false; appLogger.d('Restored display mode'); } catch (e) { appLogger.w('Failed to restore display mode', error: e); } } } Future _matchRefreshRate(double fps) async { final currentMode = await _channel.invokeMapMethod('getCurrentDisplayMode'); if (currentMode == null) return false; final currentWidth = currentMode['width'] as int; final currentHeight = currentMode['height'] as int; final currentRate = currentMode['refreshRate'] as int; // Find best matching rate from available modes. final modes = await _channel.invokeListMethod('getDisplayModes'); if (modes == null || modes.isEmpty) return false; final bestRate = _findBestRefreshRate(fps, modes, currentWidth, currentHeight); if (bestRate == 0 || bestRate == currentRate) return false; final success = await _channel.invokeMethod('setDisplayMode', { 'width': currentWidth, 'height': currentHeight, 'refreshRate': bestRate, }); if (success == true) { _displayModeChanged = true; appLogger.d('Matched refresh rate: ${fps}fps -> ${bestRate}Hz'); return true; } return false; } Future _enableSystemHDR() async { final supported = await _channel.invokeMethod('isHDRSupported'); if (supported != true) return false; final alreadyEnabled = await _channel.invokeMethod('isHDREnabled'); if (alreadyEnabled == true) return false; final success = await _channel.invokeMethod('setSystemHDR', {'enabled': true}); if (success == true) { _hdrStateChanged = true; appLogger.d('Enabled system HDR for HDR content'); return true; } return false; } /// Find the best matching refresh rate for a video fps. /// Mirrors the C++ FindBestRefreshRate algorithm. static int _findBestRefreshRate(double videoFps, List modes, int currentWidth, int currentHeight) { if (videoFps <= 0) return 0; // Collect unique refresh rates at current resolution. final rates = {}; for (final mode in modes) { final w = mode['width'] as int; final h = mode['height'] as int; if (w == currentWidth && h == currentHeight) { rates.add(mode['refreshRate'] as int); } } if (rates.isEmpty) return 0; int bestRate = 0; int bestMultiplier = 0; for (final rate in rates) { final ratio = rate / videoFps; final rounded = ratio.roundToDouble(); if (rounded < 1.0) continue; final multiplier = rounded.toInt(); final deviation = (ratio - rounded).abs() / rounded; // Within 0.5% tolerance. if (deviation > 0.005) continue; if (bestRate == 0 || multiplier < bestMultiplier || (multiplier == bestMultiplier && rate > bestRate)) { bestRate = rate; bestMultiplier = multiplier; } } return bestRate; } }