mirror of
				https://github.com/yattee/yattee.git
				synced 2025-10-31 12:41:57 +00:00 
			
		
		
		
	Merge pull request #805 from yattee/mpv-better-performance
MPV: improved A/V sync
This commit is contained in:
		| @@ -13,6 +13,7 @@ final class AdvancedSettingsGroupExporter: SettingsGroupExporter { | ||||
|             "mpvDeinterlace": Defaults[.mpvDeinterlace], | ||||
|             "mpvHWdec": Defaults[.mpvHWdec], | ||||
|             "mpvDemuxerLavfProbeInfo": Defaults[.mpvDemuxerLavfProbeInfo], | ||||
|             "mpvSetRefreshToContentFPS": Defaults[.mpvSetRefreshToContentFPS], | ||||
|             "mpvInitialAudioSync": Defaults[.mpvInitialAudioSync], | ||||
|             "showCacheStatus": Defaults[.showCacheStatus], | ||||
|             "feedCacheSize": Defaults[.feedCacheSize] | ||||
|   | ||||
| @@ -41,6 +41,10 @@ struct AdvancedSettingsGroupImporter { | ||||
|             Defaults[.mpvDemuxerLavfProbeInfo] = mpvDemuxerLavfProbeInfo | ||||
|         } | ||||
|  | ||||
|         if let mpvSetRefreshToContentFPS = json["mpvSetRefreshToContentFPS"].bool { | ||||
|             Defaults[.mpvSetRefreshToContentFPS] = mpvSetRefreshToContentFPS | ||||
|         } | ||||
|  | ||||
|         if let mpvInitialAudioSync = json["mpvInitialAudioSync"].bool { | ||||
|             Defaults[.mpvInitialAudioSync] = mpvInitialAudioSync | ||||
|         } | ||||
|   | ||||
| @@ -11,6 +11,7 @@ import SwiftUI | ||||
| final class MPVBackend: PlayerBackend { | ||||
|     static var timeUpdateInterval = 0.5 | ||||
|     static var networkStateUpdateInterval = 0.1 | ||||
|     static var refreshRateUpdateInterval = 0.5 | ||||
|  | ||||
|     private var logger = Logger(label: "mpv-backend") | ||||
|  | ||||
| @@ -24,7 +25,9 @@ final class MPVBackend: PlayerBackend { | ||||
|     var video: Video? | ||||
|     var captions: Captions? { didSet { | ||||
|         guard let captions else { | ||||
|             client?.removeSubs() | ||||
|             if client?.areSubtitlesAdded == true { | ||||
|                 client?.removeSubs() | ||||
|             } | ||||
|             return | ||||
|         } | ||||
|         addSubTrack(captions.url) | ||||
| @@ -89,6 +92,7 @@ final class MPVBackend: PlayerBackend { | ||||
|  | ||||
|     private var clientTimer: Repeater! | ||||
|     private var networkStateTimer: Repeater! | ||||
|     private var refreshRateTimer: Repeater! | ||||
|  | ||||
|     private var onFileLoaded: (() -> Void)? | ||||
|  | ||||
| @@ -184,21 +188,24 @@ final class MPVBackend: PlayerBackend { | ||||
|     } | ||||
|  | ||||
|     init() { | ||||
|         // swiftlint:disable shorthand_optional_binding | ||||
|         clientTimer = .init(interval: .seconds(Self.timeUpdateInterval), mode: .infinite) { [weak self] _ in | ||||
|             guard let self = self, self.model.activeBackend == .mpv else { | ||||
|             guard let self, self.model.activeBackend == .mpv else { | ||||
|                 return | ||||
|             } | ||||
|             self.getTimeUpdates() | ||||
|         } | ||||
|  | ||||
|         networkStateTimer = .init(interval: .seconds(Self.networkStateUpdateInterval), mode: .infinite) { [weak self] _ in | ||||
|             guard let self = self, self.model.activeBackend == .mpv else { | ||||
|             guard let self, self.model.activeBackend == .mpv else { | ||||
|                 return | ||||
|             } | ||||
|             self.updateNetworkState() | ||||
|         } | ||||
|         // swiftlint:enable shorthand_optional_binding | ||||
|  | ||||
|         refreshRateTimer = .init(interval: .seconds(Self.refreshRateUpdateInterval), mode: .infinite) { [weak self] _ in | ||||
|             guard let self, self.model.activeBackend == .mpv else { return } | ||||
|             self.checkAndUpdateRefreshRate() | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     typealias AreInIncreasingOrder = (Stream, Stream) -> Bool | ||||
| @@ -343,8 +350,17 @@ final class MPVBackend: PlayerBackend { | ||||
|         startClientUpdates() | ||||
|     } | ||||
|  | ||||
|     func startRefreshRateUpdates() { | ||||
|         refreshRateTimer.start() | ||||
|     } | ||||
|  | ||||
|     func stopRefreshRateUpdates() { | ||||
|         refreshRateTimer.pause() | ||||
|     } | ||||
|  | ||||
|     func play() { | ||||
|         startClientUpdates() | ||||
|         startRefreshRateUpdates() | ||||
|  | ||||
|         if controls.presentingControls { | ||||
|             startControlsUpdates() | ||||
| @@ -372,6 +388,7 @@ final class MPVBackend: PlayerBackend { | ||||
|  | ||||
|     func pause() { | ||||
|         stopClientUpdates() | ||||
|         stopRefreshRateUpdates() | ||||
|  | ||||
|         client?.pause() | ||||
|         isPaused = true | ||||
| @@ -391,6 +408,8 @@ final class MPVBackend: PlayerBackend { | ||||
|     } | ||||
|  | ||||
|     func stop() { | ||||
|         stopClientUpdates() | ||||
|         stopRefreshRateUpdates() | ||||
|         client?.stop() | ||||
|         isPlaying = false | ||||
|         isPaused = false | ||||
| @@ -472,6 +491,52 @@ final class MPVBackend: PlayerBackend { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private func checkAndUpdateRefreshRate() { | ||||
|         guard let screenRefreshRate = client?.getScreenRefreshRate() else { | ||||
|             logger.warning("Failed to get screen refresh rate.") | ||||
|             return | ||||
|         } | ||||
|  | ||||
|         let contentFps = client?.currentContainerFps ?? screenRefreshRate | ||||
|  | ||||
|         guard Defaults[.mpvSetRefreshToContentFPS] else { | ||||
|             // If the current refresh rate doesn't match the screen refresh rate, reset it | ||||
|             if client?.currentRefreshRate != screenRefreshRate { | ||||
|                 client?.updateRefreshRate(to: screenRefreshRate) | ||||
|                 client?.currentRefreshRate = screenRefreshRate | ||||
|                 #if !os(macOS) | ||||
|                     notifyViewToUpdateDisplayLink(with: screenRefreshRate) | ||||
|                 #endif | ||||
|                 logger.info("Reset refresh rate to screen's rate: \(screenRefreshRate) Hz") | ||||
|             } | ||||
|             return | ||||
|         } | ||||
|  | ||||
|         // Adjust the refresh rate to match the content if it differs | ||||
|         if screenRefreshRate != contentFps { | ||||
|             client?.updateRefreshRate(to: contentFps) | ||||
|             client?.currentRefreshRate = contentFps | ||||
|             #if !os(macOS) | ||||
|                 notifyViewToUpdateDisplayLink(with: contentFps) | ||||
|             #endif | ||||
|             logger.info("Adjusted screen refresh rate to match content: \(contentFps) Hz") | ||||
|         } else if client?.currentRefreshRate != screenRefreshRate { | ||||
|             // Ensure the refresh rate is set back to the screen's rate if no adjustment is needed | ||||
|             client?.updateRefreshRate(to: screenRefreshRate) | ||||
|             client?.currentRefreshRate = screenRefreshRate | ||||
|             #if !os(macOS) | ||||
|                 notifyViewToUpdateDisplayLink(with: screenRefreshRate) | ||||
|             #endif | ||||
|             logger.info("Checked and reset refresh rate to screen's rate: \(screenRefreshRate) Hz") | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     #if !os(macOS) | ||||
|         private func notifyViewToUpdateDisplayLink(with refreshRate: Int) { | ||||
|             NotificationCenter.default.post(name: .updateDisplayLinkFrameRate, object: nil, userInfo: ["refreshRate": refreshRate]) | ||||
|         } | ||||
|     #endif | ||||
|  | ||||
|     func handle(_ event: UnsafePointer<mpv_event>!) { | ||||
|         logger.info(.init(stringLiteral: "RECEIVED  event: \(String(cString: mpv_event_name(event.pointee.event_id)))")) | ||||
|  | ||||
| @@ -552,7 +617,9 @@ final class MPVBackend: PlayerBackend { | ||||
|     } | ||||
|  | ||||
|     func addSubTrack(_ url: URL) { | ||||
|         client?.removeSubs() | ||||
|         if client?.areSubtitlesAdded == true { | ||||
|             client?.removeSubs() | ||||
|         } | ||||
|         client?.addSubTrack(url) | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -6,6 +6,8 @@ import Logging | ||||
| #if !os(macOS) | ||||
|     import Siesta | ||||
|     import UIKit | ||||
| #else | ||||
|     import AppKit | ||||
| #endif | ||||
|  | ||||
| final class MPVClient: ObservableObject { | ||||
| @@ -29,6 +31,7 @@ final class MPVClient: ObservableObject { | ||||
|     var backend: MPVBackend! | ||||
|  | ||||
|     var seeking = false | ||||
|     var currentRefreshRate = 60 | ||||
|  | ||||
|     func create(frame: CGRect? = nil) { | ||||
|         #if !os(macOS) | ||||
| @@ -39,7 +42,7 @@ final class MPVClient: ObservableObject { | ||||
|  | ||||
|         mpv = mpv_create() | ||||
|         if mpv == nil { | ||||
|             print("failed creating context\n") | ||||
|             logger.critical("failed creating context\n") | ||||
|             exit(1) | ||||
|         } | ||||
|  | ||||
| @@ -76,6 +79,27 @@ final class MPVClient: ObservableObject { | ||||
|         checkError(mpv_set_option_string(mpv, "user-agent", UserAgentManager.shared.userAgent)) | ||||
|         checkError(mpv_set_option_string(mpv, "initial-audio-sync", Defaults[.mpvInitialAudioSync] ? "yes" : "no")) | ||||
|  | ||||
|         // Enable VSYNC – needed for `video-sync` | ||||
|         checkError(mpv_set_option_string(mpv, "opengl-swapinterval", "1")) | ||||
|         checkError(mpv_set_option_string(mpv, "video-sync", "display-resample")) | ||||
|         checkError(mpv_set_option_string(mpv, "interpolation", "yes")) | ||||
|         checkError(mpv_set_option_string(mpv, "tscale", "mitchell")) | ||||
|         checkError(mpv_set_option_string(mpv, "tscale-window", "blackman")) | ||||
|         checkError(mpv_set_option_string(mpv, "vd-lavc-framedrop", "nonref")) | ||||
|         checkError(mpv_set_option_string(mpv, "display-fps-override", "\(String(getScreenRefreshRate()))")) | ||||
|  | ||||
|         // CPU // | ||||
|  | ||||
|         // Determine number of threads based on system core count | ||||
|         let numberOfCores = ProcessInfo.processInfo.processorCount | ||||
|         let threads = numberOfCores * 2 | ||||
|  | ||||
|         // Log the number of cores and threads | ||||
|         logger.info("Number of CPU cores: \(numberOfCores)") | ||||
|  | ||||
|         // Set the number of threads dynamically | ||||
|         checkError(mpv_set_option_string(mpv, "vd-lavc-threads", "\(threads)")) | ||||
|  | ||||
|         // GPU // | ||||
|  | ||||
|         checkError(mpv_set_option_string(mpv, "hwdec", Defaults[.mpvHWdec])) | ||||
| @@ -83,7 +107,6 @@ final class MPVClient: ObservableObject { | ||||
|  | ||||
|         // We set set everything to OpenGL so MPV doesn't have to probe for other APIs. | ||||
|         checkError(mpv_set_option_string(mpv, "gpu-api", "opengl")) | ||||
|         checkError(mpv_set_option_string(mpv, "opengl-swapinterval", "0")) | ||||
|  | ||||
|         #if !os(macOS) | ||||
|             checkError(mpv_set_option_string(mpv, "opengl-es", "yes")) | ||||
| @@ -114,7 +137,7 @@ final class MPVClient: ObservableObject { | ||||
|             get_proc_address_ctx: nil | ||||
|         ) | ||||
|  | ||||
|         queue = DispatchQueue(label: "mpv") | ||||
|         queue = DispatchQueue(label: "mpv", qos: .userInteractive, attributes: [.concurrent]) | ||||
|  | ||||
|         withUnsafeMutablePointer(to: &initParams) { initParams in | ||||
|             var params = [ | ||||
| @@ -124,7 +147,7 @@ final class MPVClient: ObservableObject { | ||||
|             ] | ||||
|  | ||||
|             if mpv_render_context_create(&mpvGL, mpv, ¶ms) < 0 { | ||||
|                 print("failed to initialize mpv GL context") | ||||
|                 logger.critical("failed to initialize mpv GL context") | ||||
|                 exit(1) | ||||
|             } | ||||
|  | ||||
| @@ -320,6 +343,37 @@ final class MPVClient: ObservableObject { | ||||
|         mpv.isNil ? false : getFlag("eof-reached") | ||||
|     } | ||||
|  | ||||
|     var currentContainerFps: Int { | ||||
|         guard !mpv.isNil else { return 30 } | ||||
|         let fps = getDouble("container-fps") | ||||
|         return Int(fps.rounded()) | ||||
|     } | ||||
|  | ||||
|     var areSubtitlesAdded: Bool { | ||||
|         guard !mpv.isNil else { return false } | ||||
|  | ||||
|         // Retrieve the number of tracks | ||||
|         let trackCount = getInt("track-list/count") | ||||
|         guard trackCount > 0 else { return false } | ||||
|  | ||||
|         for index in 0 ..< trackCount { | ||||
|             // Get the type of each track | ||||
|             if let trackType = getString("track-list/\(index)/type"), trackType == "sub" { | ||||
|                 // Check if the subtitle track is currently selected | ||||
|                 let selected = getInt("track-list/\(index)/selected") | ||||
|                 if selected == 1 { | ||||
|                     return true | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return false | ||||
|     } | ||||
|  | ||||
|     func logCurrentFps() { | ||||
|         let fps = currentContainerFps | ||||
|         logger.info("Current container FPS: \(fps)") | ||||
|     } | ||||
|  | ||||
|     func seek(relative time: CMTime, completionHandler: ((Bool) -> Void)? = nil) { | ||||
|         guard !seeking else { | ||||
|             logger.warning("ignoring seek, another in progress") | ||||
| @@ -363,7 +417,7 @@ final class MPVClient: ObservableObject { | ||||
|                 return | ||||
|             } | ||||
|  | ||||
|             DispatchQueue.main.async { [weak self] in | ||||
|             DispatchQueue.main.async(qos: .userInteractive) { [weak self] in | ||||
|                 guard let self else { return } | ||||
|                 let model = self.backend.model | ||||
|                 let aspectRatio = self.aspectRatio > 0 && self.aspectRatio < VideoPlayerView.defaultAspectRatio ? self.aspectRatio : VideoPlayerView.defaultAspectRatio | ||||
| @@ -442,6 +496,45 @@ final class MPVClient: ObservableObject { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     func updateRefreshRate(to refreshRate: Int) { | ||||
|         setString("display-fps-override", "\(String(refreshRate))") | ||||
|         logger.info("Updated refresh rate during playback to: \(refreshRate) Hz") | ||||
|     } | ||||
|  | ||||
|     // Retrieve the screen's current refresh rate dynamically. | ||||
|     func getScreenRefreshRate() -> Int { | ||||
|         var refreshRate = 60 // Default to 60 Hz in case of failure | ||||
|  | ||||
|         #if os(macOS) | ||||
|             // macOS implementation using NSScreen | ||||
|             if let screen = NSScreen.main, | ||||
|                let displayID = screen.deviceDescription[NSDeviceDescriptionKey("NSScreenNumber")] as? CGDirectDisplayID, | ||||
|                let mode = CGDisplayCopyDisplayMode(displayID), | ||||
|                mode.refreshRate > 0 | ||||
|             { | ||||
|                 refreshRate = Int(mode.refreshRate) | ||||
|                 logger.info("Screen refresh rate: \(refreshRate) Hz") | ||||
|             } else { | ||||
|                 logger.warning("Failed to get refresh rate from NSScreen.") | ||||
|             } | ||||
|         #else | ||||
|             // iOS implementation using UIScreen with a failover | ||||
|             let mainScreen = UIScreen.main | ||||
|             refreshRate = mainScreen.maximumFramesPerSecond | ||||
|  | ||||
|             // Failover: if maximumFramesPerSecond is 0 or an unexpected value | ||||
|             if refreshRate <= 0 { | ||||
|                 refreshRate = 60 // Fallback to 60 Hz | ||||
|                 logger.warning("Failed to get refresh rate from UIScreen, falling back to 60 Hz.") | ||||
|             } else { | ||||
|                 logger.info("Screen refresh rate: \(refreshRate) Hz") | ||||
|             } | ||||
|         #endif | ||||
|  | ||||
|         currentRefreshRate = refreshRate | ||||
|         return refreshRate | ||||
|     } | ||||
|  | ||||
|     func addVideoTrack(_ url: URL) { | ||||
|         command("video-add", args: [url.absoluteString]) | ||||
|     } | ||||
|   | ||||
| @@ -534,8 +534,8 @@ final class PlayerModel: ObservableObject { | ||||
|     } | ||||
|  | ||||
|     private func handlePresentationChange() { | ||||
|         #if !os(iOS) | ||||
|             // TODO: Check whether this is neede on tvOS and macOS | ||||
|         #if os(macOS) | ||||
|             // TODO: Check whether this is needed on macOS | ||||
|             backend.setNeedsDrawing(presentingPlayer) | ||||
|         #endif | ||||
|  | ||||
| @@ -1009,23 +1009,21 @@ final class PlayerModel: ObservableObject { | ||||
|         } | ||||
|     #else | ||||
|         func handleEnterForeground() { | ||||
|             DispatchQueue.global(qos: .userInteractive).async { [weak self] in | ||||
|                 guard let self = self else { return } | ||||
|  | ||||
|                 if !self.musicMode, self.activeBackend == .mpv { | ||||
|                     self.mpvBackend.addVideoTrackFromStream() | ||||
|                     self.mpvBackend.setVideoToAuto() | ||||
|                     self.mpvBackend.controls.resetTimer() | ||||
|                 } else if !self.musicMode, self.activeBackend == .appleAVPlayer { | ||||
|                     self.avPlayerBackend.bindPlayerToLayer() | ||||
|                 } | ||||
|             } | ||||
|             #if os(iOS) | ||||
|                 OrientationTracker.shared.startDeviceOrientationTracking() | ||||
|             #endif | ||||
|  | ||||
|             #if os(tvOS) | ||||
|                 // TODO: Not sure if this is realy needed on tvOS, maybe it can be removed. | ||||
|                 setNeedsDrawing(presentingPlayer) | ||||
|             #endif | ||||
|  | ||||
|             if !musicMode, activeBackend == .mpv { | ||||
|                 mpvBackend.addVideoTrackFromStream() | ||||
|                 mpvBackend.setVideoToAuto() | ||||
|                 mpvBackend.controls.resetTimer() | ||||
|             } else if !musicMode, activeBackend == .appleAVPlayer { | ||||
|                 avPlayerBackend.bindPlayerToLayer() | ||||
|             } | ||||
|  | ||||
|             guard closePiPAndOpenPlayerOnEnteringForeground, playingInPictureInPicture else { | ||||
|                 return | ||||
|             } | ||||
|   | ||||
| @@ -368,6 +368,7 @@ extension Defaults.Keys { | ||||
|     static let mpvHWdec = Key<String>("hwdec", default: "auto-safe") | ||||
|     static let mpvDemuxerLavfProbeInfo = Key<String>("mpvDemuxerLavfProbeInfo", default: "no") | ||||
|     static let mpvInitialAudioSync = Key<Bool>("mpvInitialAudioSync", default: true) | ||||
|     static let mpvSetRefreshToContentFPS = Key<Bool>("mpvSetRefreshToContentFPS", default: false) | ||||
|  | ||||
|     static let showCacheStatus = Key<Bool>("showCacheStatus", default: false) | ||||
|     static let feedCacheSize = Key<String>("feedCacheSize", default: "50") | ||||
|   | ||||
| @@ -6,9 +6,10 @@ import OpenGLES | ||||
| final class MPVOGLView: GLKView { | ||||
|     private var logger = Logger(label: "stream.yattee.mpv.oglview") | ||||
|     private var defaultFBO: GLint? | ||||
|     private var displayLink: CADisplayLink? | ||||
|  | ||||
|     var mpvGL: UnsafeMutableRawPointer? | ||||
|     var queue = DispatchQueue(label: "stream.yattee.opengl") | ||||
|     var queue = DispatchQueue(label: "stream.yattee.opengl", qos: .userInteractive) | ||||
|     var needsDrawing = true | ||||
|  | ||||
|     override init(frame: CGRect) { | ||||
| @@ -29,6 +30,69 @@ final class MPVOGLView: GLKView { | ||||
|         enableSetNeedsDisplay = false | ||||
|  | ||||
|         fillBlack() | ||||
|         setupDisplayLink() | ||||
|         setupNotifications() | ||||
|     } | ||||
|  | ||||
|     required init?(coder aDecoder: NSCoder) { | ||||
|         super.init(coder: aDecoder) | ||||
|         setupDisplayLink() | ||||
|         setupNotifications() | ||||
|     } | ||||
|  | ||||
|     private func setupDisplayLink() { | ||||
|         displayLink = CADisplayLink(target: self, selector: #selector(updateFrame)) | ||||
|         displayLink?.add(to: .main, forMode: .common) | ||||
|     } | ||||
|  | ||||
|     // Set up observers to detect display changes and custom refresh rate updates. | ||||
|     private func setupNotifications() { | ||||
|         NotificationCenter.default.addObserver(self, selector: #selector(updateDisplayLinkFromNotification(_:)), name: .updateDisplayLinkFrameRate, object: nil) | ||||
|         NotificationCenter.default.addObserver(self, selector: #selector(screenDidChange), name: UIScreen.didConnectNotification, object: nil) | ||||
|         NotificationCenter.default.addObserver(self, selector: #selector(screenDidChange), name: UIScreen.didDisconnectNotification, object: nil) | ||||
|         NotificationCenter.default.addObserver(self, selector: #selector(screenDidChange), name: UIScreen.modeDidChangeNotification, object: nil) | ||||
|     } | ||||
|  | ||||
|     @objc private func screenDidChange(_: Notification) { | ||||
|         // Update the display link refresh rate when the screen configuration changes | ||||
|         updateDisplayLinkFrameRate() | ||||
|     } | ||||
|  | ||||
|     // Update the display link frame rate from the notification. | ||||
|     @objc private func updateDisplayLinkFromNotification(_ notification: Notification) { | ||||
|         guard let userInfo = notification.userInfo, | ||||
|               let refreshRate = userInfo["refreshRate"] as? Int else { return } | ||||
|         displayLink?.preferredFramesPerSecond = refreshRate | ||||
|         logger.info("Updated CADisplayLink frame rate to: \(refreshRate) from backend notification.") | ||||
|     } | ||||
|  | ||||
|     // Update the display link's preferred frame rate based on the current screen refresh rate. | ||||
|     private func updateDisplayLinkFrameRate() { | ||||
|         guard let displayLink else { return } | ||||
|         let refreshRate = getScreenRefreshRate() | ||||
|         displayLink.preferredFramesPerSecond = refreshRate | ||||
|         logger.info("Updated CADisplayLink preferred frames per second to: \(refreshRate)") | ||||
|     } | ||||
|  | ||||
|     // Retrieve the screen's current refresh rate dynamically. | ||||
|     private func getScreenRefreshRate() -> Int { | ||||
|         // Use the main screen's maximumFramesPerSecond property | ||||
|         let refreshRate = UIScreen.main.maximumFramesPerSecond | ||||
|         logger.info("Screen refresh rate: \(refreshRate) Hz") | ||||
|         return refreshRate | ||||
|     } | ||||
|  | ||||
|     @objc private func updateFrame() { | ||||
|         // Trigger the drawing process if needed | ||||
|         if needsDrawing { | ||||
|             setNeedsDisplay() | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     deinit { | ||||
|         // Invalidate the display link and remove observers to avoid memory leaks | ||||
|         displayLink?.invalidate() | ||||
|         NotificationCenter.default.removeObserver(self) | ||||
|     } | ||||
|  | ||||
|     func fillBlack() { | ||||
| @@ -37,35 +101,40 @@ final class MPVOGLView: GLKView { | ||||
|     } | ||||
|  | ||||
|     override func draw(_: CGRect) { | ||||
|         guard needsDrawing, let mpvGL else { | ||||
|             return | ||||
|         } | ||||
|         guard needsDrawing, let mpvGL else { return } | ||||
|  | ||||
|         // Bind the default framebuffer | ||||
|         glGetIntegerv(UInt32(GL_FRAMEBUFFER_BINDING), &defaultFBO!) | ||||
|  | ||||
|         // Get the current viewport dimensions | ||||
|         var dims: [GLint] = [0, 0, 0, 0] | ||||
|         glGetIntegerv(GLenum(GL_VIEWPORT), &dims) | ||||
|  | ||||
|         // Set up the OpenGL FBO data | ||||
|         var data = mpv_opengl_fbo( | ||||
|             fbo: Int32(defaultFBO!), | ||||
|             w: Int32(dims[2]), | ||||
|             h: Int32(dims[3]), | ||||
|             internal_format: 0 | ||||
|         ) | ||||
|  | ||||
|         // Flip Y coordinate for proper rendering | ||||
|         var flip: CInt = 1 | ||||
|         withUnsafeMutablePointer(to: &flip) { flip in | ||||
|             withUnsafeMutablePointer(to: &data) { data in | ||||
|  | ||||
|         // Render with the provided OpenGL FBO parameters | ||||
|         withUnsafeMutablePointer(to: &flip) { flipPtr in | ||||
|             withUnsafeMutablePointer(to: &data) { dataPtr in | ||||
|                 var params = [ | ||||
|                     mpv_render_param(type: MPV_RENDER_PARAM_OPENGL_FBO, data: data), | ||||
|                     mpv_render_param(type: MPV_RENDER_PARAM_FLIP_Y, data: flip), | ||||
|                     mpv_render_param(type: MPV_RENDER_PARAM_OPENGL_FBO, data: dataPtr), | ||||
|                     mpv_render_param(type: MPV_RENDER_PARAM_FLIP_Y, data: flipPtr), | ||||
|                     mpv_render_param() | ||||
|                 ] | ||||
|                 mpv_render_context_render(OpaquePointer(mpvGL), ¶ms) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     required init?(coder aDecoder: NSCoder) { | ||||
|         super.init(coder: aDecoder) | ||||
|     } | ||||
| } | ||||
|  | ||||
| extension Notification.Name { | ||||
|     static let updateDisplayLinkFrameRate = Notification.Name("updateDisplayLinkFrameRate") | ||||
| } | ||||
|   | ||||
| @@ -11,6 +11,7 @@ struct AdvancedSettings: View { | ||||
|     @Default(.mpvHWdec) private var mpvHWdec | ||||
|     @Default(.mpvDemuxerLavfProbeInfo) private var mpvDemuxerLavfProbeInfo | ||||
|     @Default(.mpvInitialAudioSync) private var mpvInitialAudioSync | ||||
|     @Default(.mpvSetRefreshToContentFPS) private var mpvSetRefreshToContentFPS | ||||
|     @Default(.showCacheStatus) private var showCacheStatus | ||||
|     @Default(.feedCacheSize) private var feedCacheSize | ||||
|     @Default(.showPlayNowInBackendContextMenu) private var showPlayNowInBackendContextMenu | ||||
| @@ -245,6 +246,12 @@ struct AdvancedSettings: View { | ||||
|                 #endif | ||||
|             } | ||||
|  | ||||
|             Toggle(isOn: $mpvSetRefreshToContentFPS) { | ||||
|                 HStack { | ||||
|                     Text("Sync refresh rate with content FPS – EXPERIMENTAL") | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if mpvEnableLogging { | ||||
|                 logButton | ||||
|             } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Arkadiusz Fal
					Arkadiusz Fal