fix(apple): use platform-specific mpv rendering

This commit is contained in:
edde746
2026-05-10 13:33:02 +02:00
parent 6f273b0aa8
commit bfb6aa01b9
12 changed files with 271 additions and 149 deletions
+2 -2
View File
@@ -11,7 +11,7 @@ PODS:
- file_picker (0.0.1):
- Flutter
- Flutter (1.0.0)
- os_media_controls (0.0.1):
- os_media_controls (0.2.1):
- Flutter
- package_info_plus (0.4.5):
- Flutter
@@ -113,7 +113,7 @@ SPEC CHECKSUMS:
device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
file_picker: 8fc6fe5e42585a217d44d22f79ec046cb8d81140
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
os_media_controls: 94cc278f5802b82b2d6373003aeb511f96718b27
os_media_controls: 3b06fe2dc0977dfa94f9c1dd73df7a55022d202f
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
Sentry: d587a8fe91ca13503ecd69a1905f3e8a0fcf61be
sentry_flutter: 31101687061fb85211ebab09ce6eb8db4e9ba74f
+1 -1
View File
@@ -781,7 +781,7 @@
repositoryURL = "https://github.com/edde746/MPVKit";
requirement = {
kind = revision;
revision = e9dc70092d8763c5cbee5ea72906cebc42f8dac8;
revision = 86dcf97f04e572abb5641e74780251a1d5634ff3;
};
};
/* End XCRemoteSwiftPackageReference section */
@@ -6,7 +6,7 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/edde746/MPVKit",
"state" : {
"revision" : "e9dc70092d8763c5cbee5ea72906cebc42f8dac8"
"revision" : "86dcf97f04e572abb5641e74780251a1d5634ff3"
}
}
],
@@ -6,7 +6,7 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/edde746/MPVKit",
"state" : {
"revision" : "e9dc70092d8763c5cbee5ea72906cebc42f8dac8"
"revision" : "86dcf97f04e572abb5641e74780251a1d5634ff3"
}
}
],
+63 -36
View File
@@ -1,4 +1,5 @@
import AVFoundation
import QuartzCore
import UIKit
/// Core MPV player using AVFoundation sample-buffer rendering for iOS/tvOS.
@@ -20,12 +21,13 @@ class MpvPlayerCore: MpvPlayerCoreBase {
self.window = window
let container = UIView(frame: window.bounds)
container.backgroundColor = .clear
container.backgroundColor = .black
container.isUserInteractionEnabled = false
let layer = MpvVideoLayer()
layer.frame = container.bounds
layer.contentsScale = window.screen.nativeScale
layer.isOpaque = true
layer.backgroundColor = UIColor.black.cgColor
layer.videoGravity = .resizeAspect
@@ -57,33 +59,35 @@ class MpvPlayerCore: MpvPlayerCoreBase {
var sampleBufferDisplayLayer: MpvVideoLayer? { videoLayer }
func setVisible(_ visible: Bool) {
guard let containerView else { return }
guard containerView != nil else { return }
isVisible = visible
if visible { refreshExternalDisplayAttachment() }
containerView.isHidden = !visible
setContainerHidden(!visible)
if !visible { mainBlankView?.isHidden = true }
}
func updateFrame(_ frame: CGRect? = nil) {
guard let videoLayer, let containerView else { return }
if let frame {
containerView.frame = frame
videoLayer.frame = containerView.bounds
} else if let superview = containerView.superview {
containerView.frame = superview.bounds
videoLayer.frame = containerView.bounds
} else if let window {
containerView.frame = window.bounds
videoLayer.frame = containerView.bounds
withoutLayerAnimations {
if let frame {
containerView.frame = frame
videoLayer.frame = containerView.bounds
} else if let superview = containerView.superview {
containerView.frame = superview.bounds
videoLayer.frame = containerView.bounds
} else if let window {
containerView.frame = window.bounds
videoLayer.frame = containerView.bounds
}
mainBlankView?.frame = window?.bounds ?? .zero
let screen = containerView.window?.screen ?? window?.screen ?? UIScreen.main
let scale = screen.nativeScale > 0 ? screen.nativeScale : screen.scale
videoLayer.contentsScale = scale
}
mainBlankView?.frame = window?.bounds ?? .zero
let screen = containerView.window?.screen ?? window?.screen ?? UIScreen.main
let scale = screen.nativeScale > 0 ? screen.nativeScale : screen.scale
videoLayer.contentsScale = scale
}
func externalDisplayDidChange() {
@@ -105,7 +109,7 @@ class MpvPlayerCore: MpvPlayerCoreBase {
setMainBlankViewVisible(false)
}
containerView.isHidden = !isVisible
setContainerHidden(!isVisible)
updateFrame()
}
@@ -122,12 +126,17 @@ class MpvPlayerCore: MpvPlayerCoreBase {
private func moveContainerView(to superview: UIView) {
guard let containerView else { return }
if containerView.superview !== superview {
containerView.removeFromSuperview()
withoutLayerAnimations {
if containerView.superview !== superview {
containerView.removeFromSuperview()
superview.insertSubview(containerView, at: 0)
} else if superview.subviews.first !== containerView {
superview.insertSubview(containerView, at: 0)
}
containerView.frame = superview.bounds
containerView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
}
containerView.frame = superview.bounds
containerView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
superview.insertSubview(containerView, at: 0)
}
private func setMainBlankViewVisible(_ visible: Bool) {
@@ -138,22 +147,37 @@ class MpvPlayerCore: MpvPlayerCoreBase {
}
let blankView = mainBlankView ?? UIView(frame: window.bounds)
blankView.backgroundColor = .black
blankView.isUserInteractionEnabled = false
blankView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
blankView.frame = window.bounds
withoutLayerAnimations {
blankView.backgroundColor = .black
blankView.isUserInteractionEnabled = false
blankView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
blankView.frame = window.bounds
if blankView.superview !== window {
blankView.removeFromSuperview()
window.insertSubview(blankView, at: 0)
} else {
window.insertSubview(blankView, at: 0)
if blankView.superview !== window {
blankView.removeFromSuperview()
window.insertSubview(blankView, at: 0)
} else if window.subviews.first !== blankView {
window.insertSubview(blankView, at: 0)
}
blankView.isHidden = false
}
blankView.isHidden = false
mainBlankView = blankView
}
private func setContainerHidden(_ hidden: Bool) {
withoutLayerAnimations {
containerView?.isHidden = hidden
}
}
private func withoutLayerAnimations(_ updates: () -> Void) {
CATransaction.begin()
CATransaction.setDisableActions(true)
updates()
CATransaction.commit()
}
/// Nudge mpv to present the current paused frame after leaving PiP.
func forceDraw() {
command(["seek", "0", "relative+exact"])
@@ -167,7 +191,10 @@ class MpvPlayerCore: MpvPlayerCoreBase {
#if os(iOS)
if #available(iOS 17.0, *) {
edrHeadroom = containerView?.window?.screen.potentialEDRHeadroom ?? 1.0
videoLayer.wantsExtendedDynamicRangeContent = hdrEnabled && sigPeak > 1.0 && edrHeadroom > 1.0
withoutLayerAnimations {
videoLayer.wantsExtendedDynamicRangeContent =
hdrEnabled && sigPeak > 1.0 && edrHeadroom > 1.0
}
}
#endif
+1 -1
View File
@@ -27,7 +27,7 @@ class AmbientLightingService {
AmbientLightingService(this._player);
bool get isEnabled => _enabled;
bool get isSupported => _player.playerType == 'mpv' && !Platform.isIOS && !Platform.isMacOS;
bool get isSupported => _player.playerType == 'mpv' && !Platform.isIOS;
/// Enable ambient lighting effect.
///
+1 -1
View File
@@ -22,7 +22,7 @@ class ShaderService {
ShaderPreset get currentPreset => _currentPreset;
static bool get isPlatformSupported => !Platform.isIOS && !Platform.isMacOS;
static bool get isPlatformSupported => !Platform.isIOS;
/// Check if the player is MPV (shaders are MPV-only)
bool get isSupported => _player.playerType == 'mpv' && isPlatformSupported;
+5 -2
View File
@@ -5,8 +5,11 @@ class MainFlutterWindow: NSWindow {
override func awakeFromNib() {
let flutterViewController = FlutterViewController()
// Enable transparency for Metal layer behind Flutter
self.backgroundColor = NSColor.clear
// Keep the window itself opaque so WindowServer does not have to blend the
// whole video window with the desktop every frame. Flutter stays clear so
// the native video layer behind it remains visible.
self.isOpaque = true
self.backgroundColor = NSColor.black
flutterViewController.backgroundColor = NSColor.clear
let windowFrame = self.frame
+16 -11
View File
@@ -1,4 +1,5 @@
import Cocoa
import QuartzCore
/// Delegate to notify the plugin of PiP lifecycle events
protocol MpvPipDelegate: AnyObject {
@@ -13,8 +14,8 @@ protocol MpvPipDelegate: AnyObject {
}
/// Encapsulates macOS Picture-in-Picture using the private PIP.framework (PIPViewController).
/// This approach wraps the existing video layer in PiP no VO switching needed.
/// mpv continues rendering to its AVFoundation display layer throughout PiP.
/// This approach wraps the existing Metal rendering layer in PiP no VO switching needed.
/// mpv continues rendering to its CAMetalLayer throughout PiP.
class MpvPipController: NSObject, PIPViewControllerDelegate {
// MARK: - Properties
@@ -39,18 +40,22 @@ class MpvPipController: NSObject, PIPViewControllerDelegate {
static var isSupported: Bool { true }
/// Enter PiP by wrapping the given video layer in a view and presenting it.
/// Enter PiP by wrapping the given Metal layer in a view and presenting it.
/// The layer continues receiving mpv frames no VO switch needed.
func startPip(videoLayer: CALayer, window: NSWindow, aspectRatio: NSSize) {
func startPip(metalLayer: CAMetalLayer, window: NSWindow, aspectRatio: NSSize) {
guard !isActive else { return }
sourceWindow = window
// Create a layer-hosting wrapper view for the video layer.
// Create a layer-hosting wrapper view for the Metal layer.
// PIPViewController resizes the view (and its root layer) as the PiP window resizes.
let videoView = NSView(frame: NSRect(origin: .zero, size: aspectRatio))
videoView.wantsLayer = true
videoView.layer = videoLayer
videoView.layer = metalLayer
// Reset drawableSize to zero so it auto-derives from the layer's bounds.
// Without this, the explicit main-window drawableSize persists in PiP.
metalLayer.drawableSize = .zero
// Create a view controller for PIPViewController
let vc = NSViewController()
@@ -92,16 +97,16 @@ class MpvPipController: NSObject, PIPViewControllerDelegate {
autoPipEnabled = enabled
}
/// Clean up after PiP closes detaches the video layer from the wrapper view
/// Clean up after PiP closes detaches the Metal layer from the wrapper view
/// so MpvPlayerCore can re-add it to the main window.
/// Returns the layer that was hosted in PiP.
/// Returns the Metal layer that was hosted in PiP.
@discardableResult
func detachLayer() -> CALayer? {
let videoLayer = pipVideoView?.layer
func detachLayer() -> CAMetalLayer? {
let metalLayer = pipVideoView?.layer as? CAMetalLayer
pipVideoView?.layer = CALayer() // detach before removing
pipVideoView = nil
pipVideoVC = nil
return videoLayer
return metalLayer
}
// MARK: - PIPViewControllerDelegate
+91 -52
View File
@@ -1,8 +1,8 @@
import AVFoundation
import Cocoa
import Libmpv
import QuartzCore
/// Core MPV player using AVFoundation sample-buffer rendering.
/// Core MPV player using Metal rendering on macOS.
class MpvPlayerCore: MpvPlayerCoreBase {
private weak var window: NSWindow?
@@ -23,28 +23,33 @@ class MpvPlayerCore: MpvPlayerCoreBase {
self.window = window
let layer = MpvVideoLayer()
let layer = MpvMetalLayer()
layer.frame = contentView.bounds
if let screen = window.screen ?? NSScreen.main {
layer.contentsScale = screen.backingScaleFactor
}
layer.framebufferOnly = true
layer.isOpaque = true
layer.backgroundColor = NSColor.black.cgColor
layer.autoresizingMask = [.layerWidthSizable, .layerHeightSizable]
layer.videoGravity = .resizeAspect
videoLayer = layer
updateEDRMode(sigPeak: lastSigPeak)
metalLayer = layer
contentView.wantsLayer = true
contentView.layer?.addSublayer(layer)
guard let contentLayer = contentView.layer else {
print("[MpvPlayerCore] No content layer")
metalLayer = nil
return false
}
attachMetalLayer(to: contentLayer, frame: contentView.bounds)
updateEDRMode(sigPeak: lastSigPeak)
print("[MpvPlayerCore] Video layer added, frame: \(layer.frame)")
print("[MpvPlayerCore] Metal layer added, frame: \(layer.frame)")
guard setupMpv() else {
print("[MpvPlayerCore] Failed to setup MPV")
layer.removeFromSuperlayer()
videoLayer = nil
metalLayer = nil
return false
}
@@ -75,23 +80,18 @@ class MpvPlayerCore: MpvPlayerCoreBase {
override func configurePlatformMpvOptions() {
guard let mpv else { return }
checkError(mpv_set_option_string(mpv, "avfoundation-composite-osd", "no"))
checkError(mpv_set_option_string(mpv, "ao", "avfoundation,coreaudio"))
}
func reattachVideoLayer() {
guard let videoLayer, let contentView = window?.contentView else { return }
func reattachMetalLayer() {
guard let contentView = window?.contentView else { return }
if videoLayer.superlayer == nil {
contentView.wantsLayer = true
contentView.layer?.insertSublayer(videoLayer, at: 0)
videoLayer.frame = contentView.bounds
if let screen = window?.screen ?? NSScreen.main {
videoLayer.contentsScale = screen.backingScaleFactor
}
contentView.wantsLayer = true
if let contentLayer = contentView.layer {
attachMetalLayer(to: contentLayer, frame: contentView.bounds)
}
print("[MpvPlayerCore] Video layer reattached to window")
print("[MpvPlayerCore] Metal layer reattached to window")
}
func forceDraw() {
@@ -102,22 +102,24 @@ class MpvPlayerCore: MpvPlayerCoreBase {
private var pausedState = true
func setVisible(_ visible: Bool) {
guard let videoLayer, !isPipActive else { return }
guard metalLayer != nil, !isPipActive else { return }
isVisible = visible
isBackgrounded = !visible
if visible {
videoLayer.removeFromSuperlayer()
if let superlayer = window?.contentView?.layer {
superlayer.insertSublayer(videoLayer, at: 0)
if let contentView = window?.contentView {
contentView.wantsLayer = true
if let superlayer = contentView.layer {
attachMetalLayer(to: superlayer, frame: contentView.bounds)
}
}
beginPlaybackActivity()
} else {
endPlaybackActivity()
}
videoLayer.isHidden = !visible
setMetalLayerHidden(!visible)
print("[MpvPlayerCore] setVisible(\(visible))")
}
@@ -131,46 +133,41 @@ class MpvPlayerCore: MpvPlayerCoreBase {
}
func updateFrame(_ frame: CGRect? = nil) {
guard let videoLayer, !isPipActive else { return }
guard let metalLayer, !isPipActive else { return }
let targetFrame: CGRect
if let frame {
videoLayer.frame = frame
targetFrame = frame
} else if let contentView = window?.contentView {
videoLayer.frame = contentView.bounds
targetFrame = contentView.bounds
} else {
return
}
if let screen = window?.screen ?? NSScreen.main {
videoLayer.contentsScale = screen.backingScaleFactor
withoutLayerAnimations {
metalLayer.frame = targetFrame
updateDrawableSize(for: metalLayer)
}
updateEDRMode(sigPeak: lastSigPeak)
}
override func updateEDRMode(sigPeak: Double) {
guard let videoLayer else { return }
guard let metalLayer else { return }
let hdrEnabled = self.hdrEnabled
var currentHeadroom: CGFloat = 1.0
var potentialHeadroom: CGFloat = 1.0
if let screen = window?.screen ?? NSScreen.main {
currentHeadroom = screen.maximumExtendedDynamicRangeColorComponentValue
potentialHeadroom = screen.maximumPotentialExtendedDynamicRangeColorComponentValue
}
let signalHeadroom = CGFloat(max(sigPeak, 1.0))
let contentHeadroom = min(signalHeadroom, potentialHeadroom)
let shouldEnableEDR = hdrEnabled && sigPeak > 1.0 && potentialHeadroom > 1.0
if #available(macOS 26.0, *) {
videoLayer.preferredDynamicRange = shouldEnableEDR ? .high : .standard
videoLayer.contentsHeadroom = shouldEnableEDR ? contentHeadroom : 0
}
if #available(macOS 15.0, *) {
videoLayer.toneMapMode = shouldEnableEDR ? .ifSupported : .automatic
}
if #available(macOS 14.0, *) {
videoLayer.wantsExtendedDynamicRangeContent = shouldEnableEDR
withoutLayerAnimations {
metalLayer.wantsExtendedDynamicRangeContent = shouldEnableEDR
}
print(
"[MpvPlayerCore] EDR mode: \(shouldEnableEDR) (hdrEnabled: \(hdrEnabled), sigPeak: \(sigPeak), potentialHeadroom: \(potentialHeadroom))"
)
}
func dispose() {
@@ -181,8 +178,8 @@ class MpvPlayerCore: MpvPlayerCoreBase {
NotificationCenter.default.removeObserver(self)
disposeSharedState(destroySynchronously: false)
videoLayer?.removeFromSuperlayer()
videoLayer = nil
metalLayer?.removeFromSuperlayer()
metalLayer = nil
isInitialized = false
print("[MpvPlayerCore] Disposed")
}
@@ -202,19 +199,19 @@ class MpvPlayerCore: MpvPlayerCoreBase {
}
@objc private func windowOcclusionDidChange(_ notification: Notification) {
guard let videoLayer, mpv != nil, !isPipActive else { return }
guard metalLayer != nil, mpv != nil, !isPipActive else { return }
let windowVisible = window?.occlusionState.contains(.visible) ?? true
if !windowVisible && !layerHiddenForOcclusion {
print("[MpvPlayerCore] Window occluded - hiding video layer")
videoLayer.isHidden = true
print("[MpvPlayerCore] Window occluded - hiding Metal layer")
setMetalLayerHidden(true)
layerHiddenForOcclusion = true
isBackgrounded = true
endPlaybackActivity()
} else if windowVisible && layerHiddenForOcclusion {
print("[MpvPlayerCore] Window visible - showing video layer")
print("[MpvPlayerCore] Window visible - showing Metal layer")
layerHiddenForOcclusion = false
videoLayer.isHidden = false
setMetalLayerHidden(!isVisible)
isBackgrounded = false
if !pausedState {
beginPlaybackActivity()
@@ -237,4 +234,46 @@ class MpvPlayerCore: MpvPlayerCoreBase {
self.playbackActivity = nil
print("[MpvPlayerCore] Ended playback activity assertion")
}
private func attachMetalLayer(to superlayer: CALayer, frame: CGRect) {
guard let metalLayer else { return }
withoutLayerAnimations {
superlayer.backgroundColor = NSColor.black.cgColor
superlayer.isOpaque = true
let needsReorder = superlayer.sublayers?.first !== metalLayer
if metalLayer.superlayer !== superlayer || needsReorder {
metalLayer.removeFromSuperlayer()
superlayer.insertSublayer(metalLayer, at: 0)
}
metalLayer.frame = frame
updateDrawableSize(for: metalLayer)
}
}
private func updateDrawableSize(for metalLayer: CAMetalLayer) {
if let screen = window?.screen ?? NSScreen.main {
let scale = screen.backingScaleFactor
metalLayer.contentsScale = scale
metalLayer.drawableSize = CGSize(
width: metalLayer.frame.width * scale,
height: metalLayer.frame.height * scale
)
}
}
private func setMetalLayerHidden(_ hidden: Bool) {
withoutLayerAnimations {
metalLayer?.isHidden = hidden
}
}
private func withoutLayerAnimations(_ updates: () -> Void) {
CATransaction.begin()
CATransaction.setDisableActions(true)
updates()
CATransaction.commit()
}
}
+7 -7
View File
@@ -158,7 +158,7 @@ class MpvPlayerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, MpvPluginS
}
}
/// Enter PiP by moving the AVFoundation video layer to a PiP window.
/// Enter PiP by moving the Metal rendering layer to a PiP window.
/// No VO switching mpv keeps rendering to the same layer.
private func enterPip(manual: Bool, result: FlutterResult? = nil) {
guard let playerCore = playerCore else {
@@ -167,8 +167,8 @@ class MpvPlayerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, MpvPluginS
])
return
}
guard let videoLayer = playerCore.videoLayer else {
result?(["success": false, "errorCode": "failed", "errorMessage": "No video layer"])
guard let metalLayer = playerCore.metalLayer else {
result?(["success": false, "errorCode": "failed", "errorMessage": "No Metal layer"])
return
}
guard let window = findFlutterWindow()?.0 else {
@@ -191,7 +191,7 @@ class MpvPlayerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, MpvPluginS
enteredPipViaAuto = !manual
playerCore.isPipActive = true
pip.startPip(videoLayer: videoLayer, window: window, aspectRatio: aspectRatio)
pip.startPip(metalLayer: metalLayer, window: window, aspectRatio: aspectRatio)
pipChannel?.invokeMethod("onPipChanged", arguments: true)
result?(["success": true])
}
@@ -353,11 +353,11 @@ extension MpvPlayerPlugin: MpvPipDelegate {
playerCore?.isPipActive = false
enteredPipViaAuto = false
// Detach the video layer from the PiP wrapper view
// Detach the Metal layer from the PiP wrapper view
pipController?.detachLayer()
// Re-attach the video layer to the main window
playerCore?.reattachVideoLayer()
// Re-attach the Metal layer to the main window
playerCore?.reattachMetalLayer()
// Force a redraw if paused (prevents black frame after PiP exit)
if playerCore?.isPaused == true {
+82 -34
View File
@@ -14,7 +14,35 @@ protocol MpvPlayerDelegate: AnyObject {
func onEvent(name: String, data: [String: Any]?)
}
class MpvVideoLayer: AVSampleBufferDisplayLayer {}
#if os(macOS)
// Workaround for MoltenVK problems that cause flicker.
// https://github.com/mpv-player/mpv/pull/13651
class MpvMetalLayer: CAMetalLayer {
override var drawableSize: CGSize {
get { super.drawableSize }
set {
if newValue == .zero || (Int(newValue.width) > 1 && Int(newValue.height) > 1) {
super.drawableSize = newValue
}
}
}
override var wantsExtendedDynamicRangeContent: Bool {
get { super.wantsExtendedDynamicRangeContent }
set {
if Thread.isMainThread {
super.wantsExtendedDynamicRangeContent = newValue
} else {
DispatchQueue.main.async {
super.wantsExtendedDynamicRangeContent = newValue
}
}
}
}
}
#else
class MpvVideoLayer: AVSampleBufferDisplayLayer {}
#endif
/// Safely convert a C string to Swift String with UTF-8 validation.
/// Falls back to Latin-1 decoding if the bytes are not valid UTF-8.
@@ -36,7 +64,11 @@ func safeString(_ cstr: UnsafePointer<CChar>) -> String {
class MpvPlayerCoreBase: NSObject {
weak var delegate: MpvPlayerDelegate?
var videoLayer: MpvVideoLayer?
#if os(macOS)
var metalLayer: MpvMetalLayer?
#else
var videoLayer: MpvVideoLayer?
#endif
var mpv: OpaquePointer?
var isInitialized = false
var isDisposing = false
@@ -100,7 +132,11 @@ class MpvPlayerCoreBase: NSObject {
func updateEDRMode(sigPeak: Double) {}
func setupMpv() -> Bool {
guard let videoLayer else { return false }
#if os(macOS)
guard let renderLayer = metalLayer else { return false }
#else
guard let renderLayer = videoLayer else { return false }
#endif
mpv = mpv_create()
guard let mpv else {
@@ -114,7 +150,7 @@ class MpvPlayerCoreBase: NSObject {
checkError(mpv_request_log_messages(mpv, "warn"))
#endif
var layer = Int64(Int(bitPattern: Unmanaged.passUnretained(videoLayer).toOpaque()))
var layer = Int64(Int(bitPattern: Unmanaged.passUnretained(renderLayer).toOpaque()))
checkError(mpv_set_option(mpv, "wid", MPV_FORMAT_INT64, &layer))
applySharedMpvOptions()
configurePlatformMpvOptions()
@@ -167,8 +203,8 @@ class MpvPlayerCoreBase: NSObject {
}
#endif
if isAvFoundationManagedProperty(name) {
print("[MpvPlayerCore] Ignoring managed AVFoundation property: \(name)=\(value)")
if isManagedRendererProperty(name) {
print("[MpvPlayerCore] Ignoring managed renderer property: \(name)=\(value)")
completion(.success(()))
return
}
@@ -356,48 +392,60 @@ class MpvPlayerCoreBase: NSObject {
private func applySharedMpvOptions() {
guard let mpv else { return }
checkError(mpv_set_option_string(mpv, "vo", "avfoundation"))
#if targetEnvironment(simulator)
checkError(mpv_set_option_string(mpv, "avfoundation-composite-osd", "no"))
checkError(mpv_set_option_string(mpv, "hwdec", "no"))
#else
checkError(mpv_set_option_string(mpv, "avfoundation-composite-osd", "yes"))
#if os(macOS)
checkError(mpv_set_option_string(mpv, "vo", "gpu-next"))
checkError(mpv_set_option_string(mpv, "gpu-api", "vulkan"))
checkError(mpv_set_option_string(mpv, "gpu-context", "moltenvk"))
checkError(mpv_set_option_string(mpv, "hwdec", "videotoolbox"))
#else
checkError(mpv_set_option_string(mpv, "vo", "avfoundation"))
#if targetEnvironment(simulator)
checkError(mpv_set_option_string(mpv, "avfoundation-composite-osd", "no"))
checkError(mpv_set_option_string(mpv, "hwdec", "no"))
#else
checkError(mpv_set_option_string(mpv, "avfoundation-composite-osd", "yes"))
checkError(mpv_set_option_string(mpv, "hwdec", "videotoolbox"))
#endif
#endif
checkError(mpv_set_option_string(mpv, "hwdec-codecs", "all"))
checkError(mpv_set_option_string(mpv, "hwdec-software-fallback", "yes"))
checkError(mpv_set_option_string(mpv, "target-colorspace-hint", "yes"))
}
private func isAvFoundationManagedProperty(_ name: String) -> Bool {
private func isManagedRendererProperty(_ name: String) -> Bool {
name == "vo" || name == "wid" || name == "gpu-api" || name == "gpu-context"
|| name == "avfoundation-composite-osd"
}
private func updateVideoGravityIfNeeded(name: String, value: String) {
let gravity: AVLayerVideoGravity
cacheLock.lock()
switch name {
case "panscan":
currentPanscan = Double(value) ?? 0
case "video-aspect-override":
aspectOverrideActive = value != "no" && value != "-1" && value != "0"
default:
cacheLock.unlock()
#if os(macOS)
return
}
#else
let gravity: AVLayerVideoGravity
cacheLock.lock()
switch name {
case "panscan":
currentPanscan = Double(value) ?? 0
case "video-aspect-override":
aspectOverrideActive = value != "no" && value != "-1" && value != "0"
default:
cacheLock.unlock()
return
}
if aspectOverrideActive {
gravity = .resize
} else if currentPanscan > 0 {
gravity = .resizeAspectFill
} else {
gravity = .resizeAspect
}
cacheLock.unlock()
if aspectOverrideActive {
gravity = .resize
} else if currentPanscan > 0 {
gravity = .resizeAspectFill
} else {
gravity = .resizeAspect
}
cacheLock.unlock()
DispatchQueue.main.async { [weak self] in
self?.videoLayer?.videoGravity = gravity
}
DispatchQueue.main.async { [weak self] in
self?.videoLayer?.videoGravity = gravity
}
#endif
}
private func cancelPendingRequests() {