mirror of
https://github.com/yattee/yattee.git
synced 2025-08-06 10:44:06 +00:00
Fixes for MPV in macOS
This commit is contained in:
@@ -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?
|
||||
|
@@ -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)
|
||||
|
@@ -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, ¶ms) < 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()
|
||||
|
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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()
|
||||
|
@@ -28,7 +28,7 @@ struct PlayerQueueItem: Hashable, Identifiable, Defaults.Serializable {
|
||||
}
|
||||
|
||||
var duration: TimeInterval {
|
||||
videoDuration ?? video.length
|
||||
videoDuration ?? video?.length ?? .zero
|
||||
}
|
||||
|
||||
var shouldRestartPlaying: Bool {
|
||||
|
Reference in New Issue
Block a user