2022-02-28 02:01:17 +05:30
|
|
|
|
import CoreMedia
|
2022-07-02 16:19:57 +05:30
|
|
|
|
import Defaults
|
2022-02-17 01:53:11 +05:30
|
|
|
|
import Foundation
|
2024-08-27 02:29:24 +05:30
|
|
|
|
import Libmpv
|
2022-02-17 01:53:11 +05:30
|
|
|
|
import Logging
|
|
|
|
|
#if !os(macOS)
|
|
|
|
|
import Siesta
|
|
|
|
|
import UIKit
|
2024-09-08 01:52:09 +05:30
|
|
|
|
#else
|
|
|
|
|
import AppKit
|
2022-02-17 01:53:11 +05:30
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
final class MPVClient: ObservableObject {
|
2022-07-07 03:38:38 +05:30
|
|
|
|
static var logFile: URL {
|
|
|
|
|
YatteeApp.logsDirectory.appendingPathComponent("yattee-\(YatteeApp.build)-mpv-log.txt")
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-17 01:53:11 +05:30
|
|
|
|
private var logger = Logger(label: "mpv-client")
|
2024-09-01 16:12:31 +05:30
|
|
|
|
private var needsDrawingCooldown = false
|
|
|
|
|
private var needsDrawingWorkItem: DispatchWorkItem?
|
2022-02-17 01:53:11 +05:30
|
|
|
|
|
|
|
|
|
var mpv: OpaquePointer!
|
|
|
|
|
var mpvGL: OpaquePointer!
|
|
|
|
|
var queue: DispatchQueue!
|
2022-02-28 02:01:17 +05:30
|
|
|
|
#if os(macOS)
|
|
|
|
|
var layer: VideoLayer!
|
|
|
|
|
var link: CVDisplayLink!
|
|
|
|
|
#else
|
|
|
|
|
var glView: MPVOGLView!
|
|
|
|
|
#endif
|
2022-02-17 01:53:11 +05:30
|
|
|
|
var backend: MPVBackend!
|
|
|
|
|
|
2022-02-17 03:21:37 +05:30
|
|
|
|
var seeking = false
|
2024-09-08 01:52:09 +05:30
|
|
|
|
var currentRefreshRate = 60
|
2022-02-17 03:21:37 +05:30
|
|
|
|
|
2022-02-28 02:01:17 +05:30
|
|
|
|
func create(frame: CGRect? = nil) {
|
|
|
|
|
#if !os(macOS)
|
2022-09-28 19:57:01 +05:30
|
|
|
|
if let frame {
|
2022-03-20 04:35:09 +05:30
|
|
|
|
glView = MPVOGLView(frame: frame)
|
|
|
|
|
}
|
2022-02-28 02:01:17 +05:30
|
|
|
|
#endif
|
2022-02-17 01:53:11 +05:30
|
|
|
|
|
|
|
|
|
mpv = mpv_create()
|
|
|
|
|
if mpv == nil {
|
|
|
|
|
print("failed creating context\n")
|
|
|
|
|
exit(1)
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-07 03:38:38 +05:30
|
|
|
|
if Defaults[.mpvEnableLogging] {
|
|
|
|
|
checkError(mpv_set_option_string(
|
|
|
|
|
mpv,
|
|
|
|
|
"log-file",
|
|
|
|
|
Self.logFile.absoluteString.replacingOccurrences(of: "file://", with: "")
|
|
|
|
|
))
|
2022-06-17 15:57:01 +05:30
|
|
|
|
checkError(mpv_request_log_messages(mpv, "debug"))
|
2022-07-07 03:38:38 +05:30
|
|
|
|
} else {
|
|
|
|
|
#if DEBUG
|
|
|
|
|
checkError(mpv_request_log_messages(mpv, "debug"))
|
|
|
|
|
#else
|
|
|
|
|
checkError(mpv_request_log_messages(mpv, "no"))
|
|
|
|
|
#endif
|
|
|
|
|
}
|
2022-02-28 02:01:17 +05:30
|
|
|
|
|
|
|
|
|
#if os(macOS)
|
|
|
|
|
checkError(mpv_set_option_string(mpv, "input-media-keys", "yes"))
|
|
|
|
|
#endif
|
2022-06-17 15:57:01 +05:30
|
|
|
|
|
2024-05-22 22:12:08 +05:30
|
|
|
|
// CACHING //
|
|
|
|
|
|
2024-05-19 07:17:05 +05:30
|
|
|
|
checkError(mpv_set_option_string(mpv, "cache-pause-initial", Defaults[.mpvCachePauseInital] ? "yes" : "no"))
|
2022-07-02 16:19:57 +05:30
|
|
|
|
checkError(mpv_set_option_string(mpv, "cache-secs", Defaults[.mpvCacheSecs]))
|
|
|
|
|
checkError(mpv_set_option_string(mpv, "cache-pause-wait", Defaults[.mpvCachePauseWait]))
|
2024-05-22 22:12:08 +05:30
|
|
|
|
|
|
|
|
|
// PLAYBACK //
|
2022-06-25 18:44:16 +05:30
|
|
|
|
checkError(mpv_set_option_string(mpv, "keep-open", "yes"))
|
2024-02-02 14:13:46 +05:30
|
|
|
|
checkError(mpv_set_option_string(mpv, "deinterlace", Defaults[.mpvDeinterlace] ? "yes" : "no"))
|
2024-05-20 20:47:52 +05:30
|
|
|
|
checkError(mpv_set_option_string(mpv, "sub-scale", Defaults[.captionsFontScaleSize]))
|
|
|
|
|
checkError(mpv_set_option_string(mpv, "sub-color", Defaults[.captionsFontColor]))
|
2024-05-19 17:07:50 +05:30
|
|
|
|
checkError(mpv_set_option_string(mpv, "user-agent", UserAgentManager.shared.userAgent))
|
2024-05-23 22:32:13 +05:30
|
|
|
|
checkError(mpv_set_option_string(mpv, "initial-audio-sync", Defaults[.mpvInitialAudioSync] ? "yes" : "no"))
|
2022-02-17 01:53:11 +05:30
|
|
|
|
|
2024-09-08 01:52:09 +05:30
|
|
|
|
// 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)"))
|
|
|
|
|
|
2024-05-22 22:12:08 +05:30
|
|
|
|
// GPU //
|
|
|
|
|
|
|
|
|
|
checkError(mpv_set_option_string(mpv, "hwdec", Defaults[.mpvHWdec]))
|
2022-02-17 01:53:11 +05:30
|
|
|
|
checkError(mpv_set_option_string(mpv, "vo", "libmpv"))
|
2024-05-22 22:12:08 +05:30
|
|
|
|
|
|
|
|
|
// 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"))
|
|
|
|
|
|
|
|
|
|
#if !os(macOS)
|
|
|
|
|
checkError(mpv_set_option_string(mpv, "opengl-es", "yes"))
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
// We set this to ordered since we use OpenGL and Apple's implementation is ancient.
|
|
|
|
|
checkError(mpv_set_option_string(mpv, "dither", "ordered"))
|
|
|
|
|
|
|
|
|
|
// DEMUXER //
|
|
|
|
|
|
|
|
|
|
// We request to test for lavf first and skip probing other demuxer.
|
|
|
|
|
checkError(mpv_set_option_string(mpv, "demuxer", "lavf"))
|
|
|
|
|
checkError(mpv_set_option_string(mpv, "audio-demuxer", "lavf"))
|
|
|
|
|
checkError(mpv_set_option_string(mpv, "sub-demuxer", "lavf"))
|
2022-12-09 05:45:19 +05:30
|
|
|
|
checkError(mpv_set_option_string(mpv, "demuxer-lavf-analyzeduration", "1"))
|
2024-05-22 22:12:08 +05:30
|
|
|
|
checkError(mpv_set_option_string(mpv, "demuxer-lavf-probe-info", Defaults[.mpvDemuxerLavfProbeInfo]))
|
2022-02-17 01:53:11 +05:30
|
|
|
|
|
2024-08-27 07:34:23 +05:30
|
|
|
|
// Disable ytdl, since it causes crashes on macOS.
|
|
|
|
|
#if os(macOS)
|
|
|
|
|
checkError(mpv_set_option_string(mpv, "ytdl", "no"))
|
|
|
|
|
#endif
|
|
|
|
|
|
2022-02-28 02:01:17 +05:30
|
|
|
|
checkError(mpv_initialize(mpv))
|
2022-02-17 01:53:11 +05:30
|
|
|
|
|
|
|
|
|
let api = UnsafeMutableRawPointer(mutating: (MPV_RENDER_API_TYPE_OPENGL as NSString).utf8String)
|
|
|
|
|
var initParams = mpv_opengl_init_params(
|
2022-02-28 02:01:17 +05:30
|
|
|
|
get_proc_address: getProcAddress,
|
2023-06-26 09:48:28 +05:30
|
|
|
|
get_proc_address_ctx: nil
|
2022-02-17 01:53:11 +05:30
|
|
|
|
)
|
|
|
|
|
|
2024-09-08 01:52:09 +05:30
|
|
|
|
queue = DispatchQueue(label: "mpv", qos: .userInteractive, attributes: [.concurrent])
|
2022-02-28 02:01:17 +05:30
|
|
|
|
|
2022-02-17 01:53:11 +05:30
|
|
|
|
withUnsafeMutablePointer(to: &initParams) { initParams in
|
|
|
|
|
var params = [
|
|
|
|
|
mpv_render_param(type: MPV_RENDER_PARAM_API_TYPE, data: api),
|
|
|
|
|
mpv_render_param(type: MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, data: initParams),
|
|
|
|
|
mpv_render_param()
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
if mpv_render_context_create(&mpvGL, mpv, ¶ms) < 0 {
|
2023-07-22 23:04:28 +05:30
|
|
|
|
print("failed to initialize mpv GL context")
|
2022-02-17 01:53:11 +05:30
|
|
|
|
exit(1)
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-28 02:01:17 +05:30
|
|
|
|
#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())
|
|
|
|
|
)
|
|
|
|
|
#endif
|
2022-02-17 01:53:11 +05:30
|
|
|
|
}
|
|
|
|
|
|
2023-09-23 20:12:46 +05:30
|
|
|
|
mpv_set_wakeup_callback(mpv, wakeUp, UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque()))
|
|
|
|
|
mpv_observe_property(mpv, 0, "pause", MPV_FORMAT_FLAG)
|
2023-09-23 21:35:13 +05:30
|
|
|
|
mpv_observe_property(mpv, 0, "core-idle", MPV_FORMAT_FLAG)
|
2022-02-17 01:53:11 +05:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func readEvents() {
|
|
|
|
|
queue?.async { [self] in
|
|
|
|
|
while self.mpv != nil {
|
|
|
|
|
let event = mpv_wait_event(self.mpv, 0)
|
|
|
|
|
if event!.pointee.event_id == MPV_EVENT_NONE {
|
|
|
|
|
break
|
|
|
|
|
}
|
2022-06-30 03:33:36 +05:30
|
|
|
|
backend?.handle(event)
|
2022-02-17 01:53:11 +05:30
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-21 02:55:06 +05:30
|
|
|
|
func loadFile(
|
|
|
|
|
_ url: URL,
|
|
|
|
|
audio: URL? = nil,
|
2024-05-13 11:24:24 +05:30
|
|
|
|
bitrate: Int? = nil,
|
|
|
|
|
kind: Stream.Kind,
|
2022-08-21 02:55:06 +05:30
|
|
|
|
sub: URL? = nil,
|
|
|
|
|
time: CMTime? = nil,
|
|
|
|
|
forceSeekable: Bool = false,
|
|
|
|
|
completionHandler: ((Int32) -> Void)? = nil
|
|
|
|
|
) {
|
2022-02-17 01:53:11 +05:30
|
|
|
|
var args = [url.absoluteString]
|
2022-05-30 21:06:26 +05:30
|
|
|
|
var options = [String]()
|
|
|
|
|
|
2022-12-09 05:45:19 +05:30
|
|
|
|
args.append("replace")
|
|
|
|
|
|
2024-05-02 20:16:43 +05:30
|
|
|
|
// needed since mpvkit 0.38.0
|
|
|
|
|
// https://github.com/mpv-player/mpv/issues/13806#issuecomment-2029818905
|
|
|
|
|
args.append("-1")
|
|
|
|
|
|
2022-12-09 05:45:19 +05:30
|
|
|
|
if let time, time.seconds > 0 {
|
2022-05-30 21:06:26 +05:30
|
|
|
|
options.append("start=\(Int(time.seconds))")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let audioURL = audio?.absoluteString {
|
|
|
|
|
options.append("audio-files-append=\"\(audioURL)\"")
|
2022-02-17 01:53:11 +05:30
|
|
|
|
}
|
|
|
|
|
|
2022-07-05 22:50:25 +05:30
|
|
|
|
if let subURL = sub?.absoluteString {
|
|
|
|
|
options.append("sub-files-append=\"\(subURL)\"")
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-21 02:55:06 +05:30
|
|
|
|
if forceSeekable {
|
2022-12-17 18:56:36 +05:30
|
|
|
|
options.append("force-seekable=yes")
|
|
|
|
|
// this is needed for peertube?
|
|
|
|
|
// options.append("stream-lavf-o=seekable=0")
|
2022-08-21 02:55:06 +05:30
|
|
|
|
}
|
2022-07-22 04:14:21 +05:30
|
|
|
|
|
2022-08-21 02:35:40 +05:30
|
|
|
|
if !options.isEmpty {
|
|
|
|
|
args.append(options.joined(separator: ","))
|
|
|
|
|
}
|
2022-05-30 21:06:26 +05:30
|
|
|
|
|
2024-05-13 11:24:24 +05:30
|
|
|
|
if kind == .hls, bitrate != 0 {
|
|
|
|
|
checkError(mpv_set_option_string(mpv, "hls-bitrate", String(describing: bitrate)))
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-17 01:53:11 +05:30
|
|
|
|
command("loadfile", args: args, returnValueCallback: completionHandler)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func play() {
|
|
|
|
|
setFlagAsync("pause", false)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func pause() {
|
|
|
|
|
setFlagAsync("pause", true)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func togglePlay() {
|
|
|
|
|
command("cycle", args: ["pause"])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func stop() {
|
|
|
|
|
command("stop")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var currentTime: CMTime {
|
2022-05-29 03:11:23 +05:30
|
|
|
|
CMTime.secondsInDefaultTimescale(mpv.isNil ? -1 : getDouble("time-pos"))
|
2022-02-17 01:53:11 +05:30
|
|
|
|
}
|
|
|
|
|
|
2022-06-16 23:14:39 +05:30
|
|
|
|
var frameDropCount: Int {
|
|
|
|
|
mpv.isNil ? 0 : getInt("frame-drop-count")
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-17 15:57:01 +05:30
|
|
|
|
var outputFps: Double {
|
|
|
|
|
mpv.isNil ? 0.0 : getDouble("estimated-vf-fps")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var hwDecoder: String {
|
|
|
|
|
mpv.isNil ? "unknown" : (getString("hwdec-current") ?? "unknown")
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-17 17:13:11 +05:30
|
|
|
|
var bufferingState: Double {
|
|
|
|
|
mpv.isNil ? 0.0 : getDouble("cache-buffering-state")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var cacheDuration: Double {
|
|
|
|
|
mpv.isNil ? 0.0 : getDouble("demuxer-cache-duration")
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-10 22:41:28 +05:30
|
|
|
|
var videoFormat: String {
|
|
|
|
|
stringOrUnknown("video-format")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var videoCodec: String {
|
|
|
|
|
stringOrUnknown("video-codec")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var currentVo: String {
|
|
|
|
|
stringOrUnknown("current-vo")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var width: String {
|
|
|
|
|
stringOrUnknown("width")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var height: String {
|
|
|
|
|
stringOrUnknown("height")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var videoBitrate: Double {
|
|
|
|
|
mpv.isNil ? 0.0 : getDouble("video-bitrate")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var audioFormat: String {
|
|
|
|
|
stringOrUnknown("audio-params/format")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var audioCodec: String {
|
|
|
|
|
stringOrUnknown("audio-codec")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var currentAo: String {
|
|
|
|
|
stringOrUnknown("current-ao")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var audioChannels: String {
|
|
|
|
|
stringOrUnknown("audio-params/channels")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var audioSampleRate: String {
|
|
|
|
|
stringOrUnknown("audio-params/samplerate")
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-09 05:51:04 +05:30
|
|
|
|
var aspectRatio: Double {
|
|
|
|
|
guard !mpv.isNil else { return VideoPlayerView.defaultAspectRatio }
|
|
|
|
|
let aspect = getDouble("video-params/aspect")
|
|
|
|
|
return aspect.isZero ? VideoPlayerView.defaultAspectRatio : aspect
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var dh: Double {
|
|
|
|
|
let defaultDh = 500.0
|
|
|
|
|
guard !mpv.isNil else { return defaultDh }
|
|
|
|
|
|
|
|
|
|
let dh = getDouble("video-params/dh")
|
|
|
|
|
return dh.isZero ? defaultDh : dh
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-17 01:53:11 +05:30
|
|
|
|
var duration: CMTime {
|
2022-05-29 03:11:23 +05:30
|
|
|
|
CMTime.secondsInDefaultTimescale(mpv.isNil ? -1 : getDouble("duration"))
|
2022-02-17 01:53:11 +05:30
|
|
|
|
}
|
|
|
|
|
|
2022-06-18 18:09:49 +05:30
|
|
|
|
var pausedForCache: Bool {
|
|
|
|
|
mpv.isNil ? false : getFlag("paused-for-cache")
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-04 02:48:27 +05:30
|
|
|
|
var eofReached: Bool {
|
|
|
|
|
mpv.isNil ? false : getFlag("eof-reached")
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-08 01:52:09 +05:30
|
|
|
|
var currentContainerFps: Int {
|
|
|
|
|
guard !mpv.isNil else { return 30 }
|
|
|
|
|
let fps = getDouble("container-fps")
|
|
|
|
|
return Int(fps.rounded())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func logCurrentFps() {
|
|
|
|
|
let fps = currentContainerFps
|
|
|
|
|
logger.info("Current container FPS: \(fps)")
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-17 01:53:11 +05:30
|
|
|
|
func seek(relative time: CMTime, completionHandler: ((Bool) -> Void)? = nil) {
|
2022-02-17 03:21:37 +05:30
|
|
|
|
guard !seeking else {
|
|
|
|
|
logger.warning("ignoring seek, another in progress")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
seeking = true
|
|
|
|
|
command("seek", args: [String(time.seconds)]) { [weak self] _ in
|
|
|
|
|
self?.seeking = false
|
2022-02-17 01:53:11 +05:30
|
|
|
|
completionHandler?(true)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func seek(to time: CMTime, completionHandler: ((Bool) -> Void)? = nil) {
|
2022-02-17 03:21:37 +05:30
|
|
|
|
guard !seeking else {
|
|
|
|
|
logger.warning("ignoring seek, another in progress")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
seeking = true
|
|
|
|
|
command("seek", args: [String(time.seconds), "absolute"]) { [weak self] _ in
|
|
|
|
|
self?.seeking = false
|
2022-02-17 01:53:11 +05:30
|
|
|
|
completionHandler?(true)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func setSize(_ width: Double, _ height: Double) {
|
2022-03-27 17:12:20 +05:30
|
|
|
|
let roundedWidth = width.rounded()
|
|
|
|
|
let roundedHeight = height.rounded()
|
|
|
|
|
|
|
|
|
|
guard width > 0, height > 0 else {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
logger.info("setting player size to \(roundedWidth),\(roundedHeight)")
|
2022-02-17 01:53:11 +05:30
|
|
|
|
#if !os(macOS)
|
2022-03-27 17:12:20 +05:30
|
|
|
|
guard roundedWidth <= UIScreen.main.bounds.width, roundedHeight <= UIScreen.main.bounds.height else {
|
2022-02-17 01:53:11 +05:30
|
|
|
|
logger.info("requested size is greater than screen size, ignoring")
|
2022-03-27 17:12:20 +05:30
|
|
|
|
logger.info("width: \(roundedWidth) <= \(UIScreen.main.bounds.width)")
|
|
|
|
|
logger.info("height: \(roundedHeight) <= \(UIScreen.main.bounds.height)")
|
2022-02-17 01:53:11 +05:30
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-08 01:52:09 +05:30
|
|
|
|
DispatchQueue.main.async(qos: .userInteractive) { [weak self] in
|
2022-11-25 02:06:05 +05:30
|
|
|
|
guard let self else { return }
|
|
|
|
|
let model = self.backend.model
|
2023-05-20 19:34:58 +05:30
|
|
|
|
let aspectRatio = self.aspectRatio > 0 && self.aspectRatio < VideoPlayerView.defaultAspectRatio ? self.aspectRatio : VideoPlayerView.defaultAspectRatio
|
|
|
|
|
let height = [model.playerSize.height, model.playerSize.width / aspectRatio].min()!
|
|
|
|
|
var insets = 0.0
|
|
|
|
|
#if os(iOS)
|
|
|
|
|
insets = OrientationTracker.shared.currentInterfaceOrientation.isPortrait ? SafeAreaModel.shared.safeArea.bottom : 0
|
|
|
|
|
#endif
|
|
|
|
|
let offsetY = max(0, model.playingFullScreen ? ((model.playerSize.height / 2.0) - ((height + insets) / 2)) : 0)
|
2022-07-09 05:51:04 +05:30
|
|
|
|
UIView.animate(withDuration: 0.2, animations: {
|
|
|
|
|
self.glView?.frame = CGRect(x: 0, y: offsetY, width: roundedWidth, height: height)
|
|
|
|
|
}) { completion in
|
|
|
|
|
if completion {
|
|
|
|
|
self.logger.info("setting player size to \(roundedWidth),\(roundedHeight) FINISHED")
|
|
|
|
|
|
|
|
|
|
self.glView?.queue.async {
|
|
|
|
|
self.glView.display()
|
|
|
|
|
}
|
2022-11-25 02:06:05 +05:30
|
|
|
|
self.backend?.controls.objectWillChange.send()
|
2022-07-09 05:51:04 +05:30
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-28 02:01:17 +05:30
|
|
|
|
#endif
|
2022-02-17 01:53:11 +05:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func setNeedsDrawing(_ needsDrawing: Bool) {
|
2024-09-01 16:12:31 +05:30
|
|
|
|
// Check if we are currently in a cooldown period
|
|
|
|
|
guard !needsDrawingCooldown else {
|
|
|
|
|
logger.info("Not drawing, cooldown in progress")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-17 01:53:11 +05:30
|
|
|
|
logger.info("needs drawing: \(needsDrawing)")
|
2024-09-01 16:12:31 +05:30
|
|
|
|
|
|
|
|
|
// Set the cooldown flag to true and cancel any existing work item
|
|
|
|
|
needsDrawingCooldown = true
|
|
|
|
|
needsDrawingWorkItem?.cancel()
|
|
|
|
|
|
2022-02-28 02:01:17 +05:30
|
|
|
|
#if !os(macOS)
|
2022-09-02 22:20:59 +05:30
|
|
|
|
glView?.needsDrawing = needsDrawing
|
2022-02-28 02:01:17 +05:30
|
|
|
|
#endif
|
2024-09-01 16:12:31 +05:30
|
|
|
|
|
|
|
|
|
// Create a new DispatchWorkItem to reset the cooldown flag after 0.1 seconds
|
|
|
|
|
let workItem = DispatchWorkItem { [weak self] in
|
|
|
|
|
self?.needsDrawingCooldown = false
|
|
|
|
|
}
|
|
|
|
|
needsDrawingWorkItem = workItem
|
|
|
|
|
|
|
|
|
|
// Schedule the cooldown reset after 0.1 seconds
|
|
|
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: workItem)
|
2022-02-17 01:53:11 +05:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func command(
|
|
|
|
|
_ command: String,
|
|
|
|
|
args: [String?] = [],
|
|
|
|
|
checkForErrors: Bool = true,
|
|
|
|
|
returnValueCallback: ((Int32) -> Void)? = nil
|
|
|
|
|
) {
|
|
|
|
|
guard mpv != nil else {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
var cargs = makeCArgs(command, args).map { $0.flatMap { UnsafePointer<CChar>(strdup($0)) } }
|
|
|
|
|
defer {
|
|
|
|
|
for ptr in cargs where ptr != nil {
|
|
|
|
|
free(UnsafeMutablePointer(mutating: ptr!))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
logger.info("\(command) -- \(args)")
|
|
|
|
|
let returnValue = mpv_command(mpv, &cargs)
|
|
|
|
|
if checkForErrors {
|
|
|
|
|
checkError(returnValue)
|
|
|
|
|
}
|
|
|
|
|
if let cb = returnValueCallback {
|
|
|
|
|
cb(returnValue)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-08 01:52:09 +05:30
|
|
|
|
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)
|
|
|
|
|
print("Screen refresh rate: \(refreshRate) Hz")
|
|
|
|
|
} else {
|
|
|
|
|
print("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
|
|
|
|
|
print("Failed to get refresh rate from UIScreen, falling back to 60 Hz.")
|
|
|
|
|
} else {
|
|
|
|
|
print("Screen refresh rate: \(refreshRate) Hz")
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
currentRefreshRate = refreshRate
|
|
|
|
|
return refreshRate
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-08 02:50:24 +05:30
|
|
|
|
func addVideoTrack(_ url: URL) {
|
|
|
|
|
command("video-add", args: [url.absoluteString])
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-05 22:50:25 +05:30
|
|
|
|
func addSubTrack(_ url: URL) {
|
|
|
|
|
command("sub-add", args: [url.absoluteString])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func removeSubs() {
|
|
|
|
|
command("sub-remove")
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-08 02:50:24 +05:30
|
|
|
|
func setVideoToAuto() {
|
2022-06-08 03:35:30 +05:30
|
|
|
|
setString("video", "1")
|
2022-06-08 02:50:24 +05:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func setVideoToNo() {
|
|
|
|
|
setString("video", "no")
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-20 06:19:32 +05:30
|
|
|
|
func setSubToAuto() {
|
|
|
|
|
setString("sub", "auto")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func setSubToNo() {
|
|
|
|
|
setString("sub", "no")
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-20 20:47:52 +05:30
|
|
|
|
func setSubFontSize(scaleSize: String) {
|
|
|
|
|
setString("sub-scale", scaleSize)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func setSubFontColor(color: String) {
|
|
|
|
|
setString("sub-color", color)
|
2024-05-20 19:04:31 +05:30
|
|
|
|
}
|
|
|
|
|
|
2022-06-08 02:50:24 +05:30
|
|
|
|
var tracksCount: Int {
|
|
|
|
|
Int(getString("track-list/count") ?? "-1") ?? -1
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-18 18:09:49 +05:30
|
|
|
|
private func getFlag(_ name: String) -> Bool {
|
|
|
|
|
var data = Int64()
|
|
|
|
|
mpv_get_property(mpv, name, MPV_FORMAT_FLAG, &data)
|
|
|
|
|
return data > 0
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-17 01:53:11 +05:30
|
|
|
|
private func setFlagAsync(_ name: String, _ flag: Bool) {
|
2022-09-02 22:20:59 +05:30
|
|
|
|
guard mpv != nil else { return }
|
2022-02-17 01:53:11 +05:30
|
|
|
|
var data: Int = flag ? 1 : 0
|
|
|
|
|
mpv_set_property_async(mpv, 0, name, MPV_FORMAT_FLAG, &data)
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-17 02:20:37 +05:30
|
|
|
|
func setDoubleAsync(_ name: String, _ value: Double) {
|
2022-11-11 03:56:25 +05:30
|
|
|
|
guard mpv != nil else { return }
|
2022-04-17 02:20:37 +05:30
|
|
|
|
var data = value
|
|
|
|
|
mpv_set_property_async(mpv, 0, name, MPV_FORMAT_DOUBLE, &data)
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-17 01:53:11 +05:30
|
|
|
|
private func getDouble(_ name: String) -> Double {
|
2022-11-11 03:56:25 +05:30
|
|
|
|
guard mpv != nil else { return 0.0 }
|
2022-02-17 01:53:11 +05:30
|
|
|
|
var data = Double()
|
|
|
|
|
mpv_get_property(mpv, name, MPV_FORMAT_DOUBLE, &data)
|
|
|
|
|
return data
|
|
|
|
|
}
|
2022-06-08 02:50:24 +05:30
|
|
|
|
|
|
|
|
|
private func getInt(_ name: String) -> Int {
|
2022-11-11 03:56:25 +05:30
|
|
|
|
guard mpv != nil else { return 0 }
|
2022-06-08 02:50:24 +05:30
|
|
|
|
var data = Int64()
|
|
|
|
|
mpv_get_property(mpv, name, MPV_FORMAT_INT64, &data)
|
|
|
|
|
return Int(data)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func getString(_ name: String) -> String? {
|
2023-09-23 20:12:46 +05:30
|
|
|
|
guard mpv != nil else { return nil }
|
2022-06-08 02:50:24 +05:30
|
|
|
|
let cstr = mpv_get_property_string(mpv, name)
|
|
|
|
|
let str: String? = cstr == nil ? nil : String(cString: cstr!)
|
|
|
|
|
mpv_free(cstr)
|
|
|
|
|
return str
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func setString(_ name: String, _ value: String) {
|
2022-12-21 22:43:41 +05:30
|
|
|
|
guard mpv != nil else { return }
|
2022-06-08 02:50:24 +05:30
|
|
|
|
mpv_set_property_string(mpv, name, value)
|
|
|
|
|
}
|
2022-02-17 01:53:11 +05:30
|
|
|
|
|
|
|
|
|
private func makeCArgs(_ command: String, _ args: [String?]) -> [String?] {
|
|
|
|
|
if !args.isEmpty, args.last == nil {
|
|
|
|
|
fatalError("Command do not need a nil suffix")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var strArgs = args
|
|
|
|
|
strArgs.insert(command, at: 0)
|
|
|
|
|
strArgs.append(nil)
|
|
|
|
|
|
|
|
|
|
return strArgs
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-27 03:53:34 +05:30
|
|
|
|
private func checkError(_ status: CInt) {
|
2022-02-17 01:53:11 +05:30
|
|
|
|
if status < 0 {
|
|
|
|
|
logger.error(.init(stringLiteral: "MPV API error: \(String(cString: mpv_error_string(status)))\n"))
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-06-27 03:53:34 +05:30
|
|
|
|
|
2022-11-10 22:41:28 +05:30
|
|
|
|
private func stringOrUnknown(_ name: String) -> String {
|
|
|
|
|
mpv.isNil ? "unknown" : (getString(name) ?? "unknown")
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-27 03:53:34 +05:30
|
|
|
|
private var machine: String {
|
|
|
|
|
var systeminfo = utsname()
|
|
|
|
|
uname(&systeminfo)
|
|
|
|
|
return withUnsafeBytes(of: &systeminfo.machine) { bufPtr -> String in
|
|
|
|
|
let data = Data(bufPtr)
|
|
|
|
|
if let lastIndex = data.lastIndex(where: { $0 != 0 }) {
|
|
|
|
|
return String(data: data[0 ... lastIndex], encoding: .isoLatin1)!
|
|
|
|
|
}
|
2023-06-17 17:39:51 +05:30
|
|
|
|
return String(data: data, encoding: .isoLatin1)!
|
2022-06-27 03:53:34 +05:30
|
|
|
|
}
|
|
|
|
|
}
|
2022-02-17 01:53:11 +05:30
|
|
|
|
}
|
|
|
|
|
|
2022-02-28 02:01:17 +05:30
|
|
|
|
#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)
|
2022-02-17 01:53:11 +05:30
|
|
|
|
|
2022-02-28 02:01:17 +05:30
|
|
|
|
return CFBundleGetFunctionPointerForName(identifier, symbolName)
|
|
|
|
|
}
|
2022-02-17 01:53:11 +05:30
|
|
|
|
|
2022-02-28 02:01:17 +05:30
|
|
|
|
func glUpdate(_ ctx: UnsafeMutableRawPointer?) {
|
|
|
|
|
let videoLayer = unsafeBitCast(ctx, to: VideoLayer.self)
|
2022-02-17 01:53:11 +05:30
|
|
|
|
|
2022-02-28 02:01:17 +05:30
|
|
|
|
videoLayer.client?.queue?.async {
|
|
|
|
|
if !videoLayer.isAsynchronous {
|
|
|
|
|
videoLayer.display()
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-02-17 01:53:11 +05:30
|
|
|
|
}
|
2022-02-28 02:01:17 +05:30
|
|
|
|
#else
|
|
|
|
|
func getProcAddress(_: UnsafeMutableRawPointer?, _ name: UnsafePointer<Int8>?) -> UnsafeMutableRawPointer? {
|
|
|
|
|
let symbolName = CFStringCreateWithCString(kCFAllocatorDefault, name, CFStringBuiltInEncodings.ASCII.rawValue)
|
|
|
|
|
let identifier = CFBundleGetBundleWithIdentifier("com.apple.opengles" as CFString)
|
2022-02-17 01:53:11 +05:30
|
|
|
|
|
2022-02-28 02:01:17 +05:30
|
|
|
|
return CFBundleGetFunctionPointerForName(identifier, symbolName)
|
2022-02-17 01:53:11 +05:30
|
|
|
|
}
|
|
|
|
|
|
2022-02-28 02:01:17 +05:30
|
|
|
|
private func glUpdate(_ ctx: UnsafeMutableRawPointer?) {
|
|
|
|
|
let glView = unsafeBitCast(ctx, to: MPVOGLView.self)
|
|
|
|
|
|
2022-06-17 15:57:01 +05:30
|
|
|
|
guard glView.needsDrawing else {
|
|
|
|
|
return
|
|
|
|
|
}
|
2022-06-16 23:14:39 +05:30
|
|
|
|
|
2022-06-17 15:57:01 +05:30
|
|
|
|
glView.queue.async {
|
2022-06-16 05:33:15 +05:30
|
|
|
|
glView.display()
|
2022-02-28 02:01:17 +05:30
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endif
|
2022-02-17 01:53:11 +05:30
|
|
|
|
private func wakeUp(_ context: UnsafeMutableRawPointer?) {
|
|
|
|
|
let client = unsafeBitCast(context, to: MPVClient.self)
|
|
|
|
|
client.readEvents()
|
|
|
|
|
}
|