/* * This file is part of mpv. * * mpv is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * mpv is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with mpv. If not, see . */ import Cocoa protocol EventSubscriber: AnyObject { var uid: Int { get } func handle(event: EventHelper.Event) } extension EventSubscriber { var uid: Int { return Int(bitPattern: ObjectIdentifier(self)) } } extension EventHelper { typealias WakeupCallback = (@convention(c) (UnsafeMutableRawPointer?) -> Void)? struct Event { var id: String { return name + (name.starts(with: "MPV_EVENT_") ? "" : String(format.rawValue)) } var idReset: String { return name + (name.starts(with: "MPV_EVENT_") ? "" : String(MPV_FORMAT_NONE.rawValue)) } let name: String let format: mpv_format let string: String? let bool: Bool? let int: Int64? let double: Double? let array: [Any?] init( name: String = "", format: mpv_format = MPV_FORMAT_NONE, string: String? = nil, bool: Bool? = nil, int: Int64? = nil, double: Double? = nil, array: [Any?] = [] ) { self.name = name self.format = format self.string = string self.bool = bool self.int = int self.double = double self.array = array } } } class EventHelper { unowned let appHub: AppHub var mpv: OpaquePointer? var events: [String: [Int: EventSubscriber]] = [:] init?(_ appHub: AppHub, _ mpv: OpaquePointer) { if !appHub.isApplication { mpv_destroy(mpv) return nil } self.appHub = appHub self.mpv = mpv mpv_set_wakeup_callback(mpv, wakeup, TypeHelper.bridge(obj: self)) } func subscribe(_ subscriber: EventSubscriber, event: Event) { guard let mpv = mpv else { return } if !event.name.isEmpty { if !events.keys.contains(event.idReset) { events[event.idReset] = [:] } if !events.keys.contains(event.id) { mpv_observe_property(mpv, 0, event.name, event.format) events[event.id] = [:] } events[event.idReset]?[subscriber.uid] = subscriber events[event.id]?[subscriber.uid] = subscriber } } let wakeup: WakeupCallback = { ( ctx ) in let event = unsafeBitCast(ctx, to: EventHelper.self) DispatchQueue.main.async { event.eventLoop() } } func eventLoop() { while let mpv = mpv, let event = mpv_wait_event(mpv, 0) { if event.pointee.event_id == MPV_EVENT_NONE { break } handle(event: event) } } func handle(event: UnsafeMutablePointer) { switch event.pointee.event_id { case MPV_EVENT_PROPERTY_CHANGE: handle(property: event) default: for (_, subscriber) in events[String(describing: event.pointee.event_id)] ?? [:] { subscriber.handle(event: .init(name: String(describing: event.pointee.event_id))) } } if event.pointee.event_id == MPV_EVENT_SHUTDOWN { mpv_destroy(mpv) mpv = nil } } func handle(property mpvEvent: UnsafeMutablePointer) { let pData = OpaquePointer(mpvEvent.pointee.data) guard let property = UnsafePointer(pData)?.pointee else { return } let name = String(cString: property.name) let format = property.format for (_, subscriber) in events[name + String(format.rawValue)] ?? [:] { var event: Event? switch format { case MPV_FORMAT_STRING: event = .init(name: name, format: format, string: TypeHelper.toString(property.data)) case MPV_FORMAT_FLAG: event = .init(name: name, format: format, bool: TypeHelper.toBool(property.data)) case MPV_FORMAT_DOUBLE: event = .init(name: name, format: format, double: TypeHelper.toDouble(property.data)) case MPV_FORMAT_NODE: let node = TypeHelper.toNode(property.data) event = .init( name: name, format: format, string: TypeHelper.nodeToString(node), bool: TypeHelper.nodeToBool(node), int: TypeHelper.nodeToInt(node), double: TypeHelper.nodeToDouble(node), array: TypeHelper.nodeToArray(node) ) case MPV_FORMAT_NONE: event = .init(name: name, format: format) default: break } if let e = event { subscriber.handle(event: e) } } } }