diff --git a/iina/Base.lproj/PrefCodecViewController.xib b/iina/Base.lproj/PrefCodecViewController.xib index b6cf8a6a0d6..372a7de95ba 100644 --- a/iina/Base.lproj/PrefCodecViewController.xib +++ b/iina/Base.lproj/PrefCodecViewController.xib @@ -1,8 +1,8 @@ - + - + @@ -27,11 +27,11 @@ - + - + @@ -39,7 +39,7 @@ - + @@ -47,7 +47,7 @@ - + @@ -68,7 +68,7 @@ - + @@ -76,13 +76,13 @@ - + - + @@ -97,7 +97,7 @@ - + @@ -105,7 +105,7 @@ - + @@ -113,7 +113,7 @@ - + + + + + + + Switch to a matching refresh rate (if there is any) when the player goes fullscreen. This can eliminate stuttering, and, on some external displays, enable frame interpolation. + + + + + + + + - @@ -152,17 +174,20 @@ + + + - + - + @@ -203,7 +228,7 @@ - + @@ -233,7 +258,7 @@ - + @@ -334,13 +359,13 @@ - + - + diff --git a/iina/MainWindowController.swift b/iina/MainWindowController.swift index fbe47207add..7180241cbed 100644 --- a/iina/MainWindowController.swift +++ b/iina/MainWindowController.swift @@ -1129,11 +1129,43 @@ class MainWindowController: PlayerWindowController { NSMenu.setMenuBarVisible(true) } + private var userDisplay: UInt32? + private var userDisplayMode: CGDisplayMode? + private var userRefreshRate: Double? + func windowWillEnterFullScreen(_ notification: Notification) { if isInInteractiveMode { exitInteractiveMode(immediately: true) } + // Match refresh rate + if Preference.bool(for: .matchRefreshRate) { + // [23.976, 47.952, 24, 48], [29.97, 59.94, 30, 60] + // [24, 48], [25, 50], [30, 60] + let videoFps = player.mpv.getDouble(MPVProperty.containerFps) + let refreshRates = [videoFps, videoFps * 2, videoFps.rounded(), videoFps.rounded() * 2] + + userDisplay = videoView.currentDisplay + userDisplayMode = CGDisplayCopyDisplayMode(userDisplay!) + userRefreshRate = player.mpv.getDouble(MPVProperty.displayFps) + + if (userDisplayMode != nil) { + let displayModes = CGDisplayCopyAllDisplayModes(userDisplay!, nil) as! [CGDisplayMode] + matching: for refreshRate in refreshRates { + for displayMode in displayModes { + // 24 - 23.976 = 0.024, avoid matching 23.976 to 24 when 23.976 is available + // or vice versa on first pass, prefer 47.952 than 24 for 23.976 + if ((displayMode.ioFlags & UInt32(kDisplayModeNativeFlag) != 0) && + displayMode.refreshRate.distance(to: refreshRate) < 0.02) { + CGDisplaySetDisplayMode(userDisplay!, displayMode, nil) + player.mpv.setDouble(MPVOption.Video.displayFps, displayMode.refreshRate) + break matching + } + } + } + } + } + // Set the appearance to match the theme so the titlebar matches the theme let iinaTheme = Preference.enum(for: .themeMaterial) as Preference.Theme if #available(macOS 10.14, *) { @@ -1213,6 +1245,12 @@ class MainWindowController: PlayerWindowController { exitInteractiveMode(immediately: true) } + // restore refresh rate + if userDisplayMode != nil { + CGDisplaySetDisplayMode(userDisplay!, userDisplayMode!, nil) + player.mpv.setDouble(MPVOption.Video.displayFps, userRefreshRate!) + } + // show titleBarView if oscPosition == .top { oscTopMainViewTopConstraint.constant = OSCTopMainViewMarginTop diff --git a/iina/Preference.swift b/iina/Preference.swift index 968c24e33fe..0a02e460d8d 100644 --- a/iina/Preference.swift +++ b/iina/Preference.swift @@ -149,6 +149,7 @@ struct Preference { static let videoThreads = Key("videoThreads") static let hardwareDecoder = Key("hardwareDecoder") static let forceDedicatedGPU = Key("forceDedicatedGPU") + static let matchRefreshRate = Key("matchRefreshRate") static let audioThreads = Key("audioThreads") static let audioLanguage = Key("audioLanguage") @@ -726,6 +727,7 @@ struct Preference { .videoThreads: 0, .hardwareDecoder: HardwareDecoderOption.auto.rawValue, .forceDedicatedGPU: false, + .matchRefreshRate: false, .audioThreads: 0, .audioLanguage: "", .maxVolume: 100,