Fixes for MPV in macOS

This commit is contained in:
Arkadiusz Fal
2022-02-27 21:31:17 +01:00
parent d32b38c352
commit 79118ff7e2
54 changed files with 695 additions and 249 deletions

View File

@@ -2,7 +2,9 @@ import AVFoundation
import Defaults
import Foundation
import MediaPlayer
import UIKit
#if !os(macOS)
import UIKit
#endif
final class AVPlayerBackend: PlayerBackend {
static let assetKeysToLoad = ["tracks", "playable", "duration"]
@@ -34,6 +36,7 @@ final class AVPlayerBackend: PlayerBackend {
}
private(set) var avPlayer = AVPlayer()
var controller: AppleAVPlayerViewController?
private var asset: AVURLAsset?

View File

@@ -15,7 +15,11 @@ final class MPVBackend: PlayerBackend {
var currentTime: CMTime?
var loadedVideo = false
var isLoadingVideo = true
var isLoadingVideo = true { didSet {
DispatchQueue.main.async { [weak self] in
self?.controls.isLoadingVideo = self?.isLoadingVideo ?? true
}
}}
var isPlaying = true { didSet {
if isPlaying {
@@ -28,7 +32,9 @@ final class MPVBackend: PlayerBackend {
}}
var playerItemDuration: CMTime?
var controller: MPVViewController!
#if !os(macOS)
var controller: MPVViewController!
#endif
var client: MPVClient! { didSet { client.backend = self } }
private var clientTimer: RepeatingTimer!
@@ -104,26 +110,26 @@ final class MPVBackend: PlayerBackend {
self.stop()
if let url = stream.singleAssetURL {
self.onFileLoaded = { [weak self] in
self?.setIsLoadingVideo(false)
self.onFileLoaded = {
updateCurrentStream()
startPlaying()
}
self.client.loadFile(url, time: time) { [weak self] _ in
self?.setIsLoadingVideo(true)
self?.isLoadingVideo = true
}
} else {
self.onFileLoaded = { [weak self] in
self?.client.addAudio(stream.audioAsset.url) { _ in
self?.setIsLoadingVideo(false)
updateCurrentStream()
startPlaying()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
self?.client.addAudio(stream.audioAsset.url) { _ in
updateCurrentStream()
startPlaying()
}
}
}
self.client.loadFile(stream.videoAsset.url, time: time) { [weak self] _ in
self?.setIsLoadingVideo(true)
self?.isLoadingVideo = true
self?.pause()
}
}
@@ -245,13 +251,6 @@ final class MPVBackend: PlayerBackend {
}
}
private func setIsLoadingVideo(_ value: Bool) {
isLoadingVideo = value
DispatchQueue.main.async { [weak self] in
self?.controls.isLoadingVideo = value
}
}
func handle(_ event: UnsafePointer<mpv_event>!) {
logger.info("\(String(cString: mpv_event_name(event.pointee.event_id)))")
@@ -270,6 +269,9 @@ final class MPVBackend: PlayerBackend {
onFileLoaded?()
onFileLoaded = nil
case MPV_EVENT_UNPAUSE:
isLoadingVideo = false
case MPV_EVENT_END_FILE:
DispatchQueue.main.async { [weak self] in
self?.handleEndOfFile(event)

View File

@@ -1,7 +1,7 @@
import CoreMedia
import Foundation
import Logging
#if !os(macOS)
import CoreMedia
import Siesta
import UIKit
#endif
@@ -12,13 +12,22 @@ final class MPVClient: ObservableObject {
var mpv: OpaquePointer!
var mpvGL: OpaquePointer!
var queue: DispatchQueue!
var glView: MPVOGLView!
#if os(macOS)
var layer: VideoLayer!
var link: CVDisplayLink!
#else
var glView: MPVOGLView!
#endif
var backend: MPVBackend!
var seeking = false
func create(frame: CGRect) -> MPVOGLView {
glView = MPVOGLView(frame: frame)
func create(frame: CGRect? = nil) {
#if !os(macOS)
if let frame = frame {
glView = MPVOGLView(frame: frame)
}
#endif
mpv = mpv_create()
if mpv == nil {
@@ -27,20 +36,27 @@ final class MPVClient: ObservableObject {
}
checkError(mpv_request_log_messages(mpv, "warn"))
checkError(mpv_initialize(mpv))
checkError(mpv_set_option_string(mpv, "vo", "libmpv"))
checkError(mpv_set_option_string(mpv, "hwdec", "yes"))
checkError(mpv_set_option_string(mpv, "override-display-fps", "\(UIScreen.main.maximumFramesPerSecond)"))
checkError(mpv_set_option_string(mpv, "video-sync", "display-resample"))
#if os(macOS)
checkError(mpv_set_option_string(mpv, "input-media-keys", "yes"))
#else
checkError(mpv_set_option_string(mpv, "hwdec", "yes"))
checkError(mpv_set_option_string(mpv, "override-display-fps", "\(UIScreen.main.maximumFramesPerSecond)"))
checkError(mpv_set_option_string(mpv, "video-sync", "display-resample"))
#endif
checkError(mpv_set_option_string(mpv, "vo", "libmpv"))
checkError(mpv_initialize(mpv))
let api = UnsafeMutableRawPointer(mutating: (MPV_RENDER_API_TYPE_OPENGL as NSString).utf8String)
var initParams = mpv_opengl_init_params(
get_proc_address: getProcAddress(_:_:),
get_proc_address: getProcAddress,
get_proc_address_ctx: nil,
extra_exts: nil
)
queue = DispatchQueue(label: "mpv", qos: .background)
withUnsafeMutablePointer(to: &initParams) { initParams in
var params = [
mpv_render_param(type: MPV_RENDER_PARAM_API_TYPE, data: api),
@@ -48,28 +64,31 @@ final class MPVClient: ObservableObject {
mpv_render_param()
]
var mpvGL: OpaquePointer?
if mpv_render_context_create(&mpvGL, mpv, &params) < 0 {
puts("failed to initialize mpv GL context")
exit(1)
}
glView.mpvGL = UnsafeMutableRawPointer(mpvGL)
#if os(macOS)
mpv_render_context_set_update_callback(
mpvGL,
glUpdate,
UnsafeMutableRawPointer(Unmanaged.passUnretained(layer).toOpaque())
)
#else
glView.mpvGL = UnsafeMutableRawPointer(mpvGL)
mpv_render_context_set_update_callback(
mpvGL,
glUpdate(_:),
UnsafeMutableRawPointer(Unmanaged.passUnretained(glView).toOpaque())
)
mpv_render_context_set_update_callback(
mpvGL,
glUpdate(_:),
UnsafeMutableRawPointer(Unmanaged.passUnretained(glView).toOpaque())
)
#endif
}
queue = DispatchQueue(label: "mpv", qos: .background)
queue!.async {
mpv_set_wakeup_callback(self.mpv, wakeUp, UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque()))
}
return glView
}
func readEvents() {
@@ -155,14 +174,16 @@ final class MPVClient: ObservableObject {
logger.info("requested size is greater than screen size, ignoring")
return
}
#endif
glView?.frame = CGRect(x: 0, y: 0, width: width, height: height)
glView?.frame = CGRect(x: 0, y: 0, width: width, height: height)
#endif
}
func setNeedsDrawing(_ needsDrawing: Bool) {
logger.info("needs drawing: \(needsDrawing)")
glView.needsDrawing = needsDrawing
#if !os(macOS)
glView.needsDrawing = needsDrawing
#endif
}
func command(
@@ -220,25 +241,44 @@ final class MPVClient: ObservableObject {
}
}
private func getProcAddress(_: UnsafeMutableRawPointer?, _ name: UnsafePointer<Int8>?) -> UnsafeMutableRawPointer? {
let symbolName = CFStringCreateWithCString(kCFAllocatorDefault, name, CFStringBuiltInEncodings.ASCII.rawValue)
let addr = CFBundleGetFunctionPointerForName(CFBundleGetBundleWithIdentifier("com.apple.opengles" as CFString), symbolName)
#if os(macOS)
func getProcAddress(_: UnsafeMutableRawPointer?, _ name: UnsafePointer<Int8>?) -> UnsafeMutableRawPointer? {
let symbolName = CFStringCreateWithCString(kCFAllocatorDefault, name, CFStringBuiltInEncodings.ASCII.rawValue)
let identifier = CFBundleGetBundleWithIdentifier("com.apple.opengl" as CFString)
return addr
}
private func glUpdate(_ ctx: UnsafeMutableRawPointer?) {
let glView = unsafeBitCast(ctx, to: MPVOGLView.self)
guard glView.needsDrawing else {
return
return CFBundleGetFunctionPointerForName(identifier, symbolName)
}
DispatchQueue.main.async {
glView.setNeedsDisplay()
}
}
func glUpdate(_ ctx: UnsafeMutableRawPointer?) {
let videoLayer = unsafeBitCast(ctx, to: VideoLayer.self)
videoLayer.client?.queue?.async {
if !videoLayer.isAsynchronous {
videoLayer.display()
}
}
}
#else
func getProcAddress(_: UnsafeMutableRawPointer?, _ name: UnsafePointer<Int8>?) -> UnsafeMutableRawPointer? {
let symbolName = CFStringCreateWithCString(kCFAllocatorDefault, name, CFStringBuiltInEncodings.ASCII.rawValue)
let identifier = CFBundleGetBundleWithIdentifier("com.apple.opengles" as CFString)
return CFBundleGetFunctionPointerForName(identifier, symbolName)
}
private func glUpdate(_ ctx: UnsafeMutableRawPointer?) {
let glView = unsafeBitCast(ctx, to: MPVOGLView.self)
guard glView.needsDrawing else {
return
}
DispatchQueue.main.async {
glView.setNeedsDisplay()
}
}
#endif
private func wakeUp(_ context: UnsafeMutableRawPointer?) {
let client = unsafeBitCast(context, to: MPVClient.self)
client.readEvents()

View File

@@ -82,14 +82,16 @@ final class PlayerControlsModel: ObservableObject {
playingFullscreen = !value
}
if playingFullscreen {
guard !(UIApplication.shared.windows.first?.windowScene?.interfaceOrientation.isLandscape ?? true) else {
return
#if os(iOS)
if playingFullscreen {
guard !(UIApplication.shared.windows.first?.windowScene?.interfaceOrientation.isLandscape ?? true) else {
return
}
Orientation.lockOrientation(.landscape, andRotateTo: .landscapeRight)
} else {
Orientation.lockOrientation(.allButUpsideDown, andRotateTo: .portrait)
}
Orientation.lockOrientation(.landscape, andRotateTo: .landscapeRight)
} else {
Orientation.lockOrientation(.allButUpsideDown, andRotateTo: .portrait)
}
#endif
}
}

View File

@@ -18,7 +18,7 @@ final class PlayerModel: ObservableObject {
static let availableRates: [Float] = [0.5, 0.67, 0.8, 1, 1.25, 1.5, 2]
let logger = Logger(label: "stream.yattee.app")
var avPlayerView = AVPlayerView()
var avPlayerView = AppleAVPlayerView()
var playerItem: AVPlayerItem?
var mpvPlayerView = MPVPlayerView()

View File

@@ -28,7 +28,7 @@ struct PlayerQueueItem: Hashable, Identifiable, Defaults.Serializable {
}
var duration: TimeInterval {
videoDuration ?? video.length
videoDuration ?? video?.length ?? .zero
}
var shouldRestartPlaying: Bool {