summaryrefslogtreecommitdiffstats
path: root/osdep/mac/remote_command_center.swift
diff options
context:
space:
mode:
authorder richter <der.richter@gmx.de>2024-02-27 16:55:29 +0100
committerder richter <der.richter@gmx.de>2024-02-28 15:52:47 +0100
commit86fa9b18a3619a379a597ca0902c23dc053cafc0 (patch)
treef187f85a97744bd0468290975e5ec4d10054b25b /osdep/mac/remote_command_center.swift
parent661f45377a17635125ae36f0b8a3487ae13cf606 (diff)
downloadmpv-86fa9b18a3619a379a597ca0902c23dc053cafc0.tar.bz2
mpv-86fa9b18a3619a379a597ca0902c23dc053cafc0.tar.xz
osdep/mac: make mac naming of files, folders and function consistent
rename all macOS namings (osx, macosx, macOS, macos, apple) to mac, to make naming consistent.
Diffstat (limited to 'osdep/mac/remote_command_center.swift')
-rw-r--r--osdep/mac/remote_command_center.swift214
1 files changed, 214 insertions, 0 deletions
diff --git a/osdep/mac/remote_command_center.swift b/osdep/mac/remote_command_center.swift
new file mode 100644
index 0000000000..f6613a1f9a
--- /dev/null
+++ b/osdep/mac/remote_command_center.swift
@@ -0,0 +1,214 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+import MediaPlayer
+
+extension RemoteCommandCenter {
+ typealias ConfigHandler = (MPRemoteCommandEvent) -> (MPRemoteCommandHandlerStatus)
+
+ enum KeyType {
+ case normal
+ case repeatable
+ }
+
+ struct Config {
+ let key: Int32
+ let type: KeyType
+ var state: UInt32 = 0
+ let handler: ConfigHandler
+
+ init(key: Int32 = 0, type: KeyType = .normal, handler: @escaping ConfigHandler = { event in return .commandFailed }) {
+ self.key = key
+ self.type = type
+ self.handler = handler
+ }
+ }
+}
+
+class RemoteCommandCenter: NSObject {
+ var configs: [MPRemoteCommand:Config] = [:]
+ var disabledCommands: [MPRemoteCommand] = []
+ var isPaused: Bool = false { didSet { updateInfoCenter() } }
+ var duration: Double = 0 { didSet { updateInfoCenter() } }
+ var position: Double = 0 { didSet { updateInfoCenter() } }
+ var rate: Double = 0 { didSet { updateInfoCenter() } }
+ var title: String = "" { didSet { updateInfoCenter() } }
+ var chapter: String? { didSet { updateInfoCenter() } }
+ var album: String? { didSet { updateInfoCenter() } }
+ var artist: String? { didSet { updateInfoCenter() } }
+ var cover: NSImage = NSImage(size: NSSize(width: 256, height: 256))
+
+ var infoCenter: MPNowPlayingInfoCenter { get { return MPNowPlayingInfoCenter.default() } }
+ var commandCenter: MPRemoteCommandCenter { get { return MPRemoteCommandCenter.shared() } }
+
+ @objc override init() {
+ super.init()
+
+ configs = [
+ commandCenter.pauseCommand: Config(key: MP_KEY_PAUSEONLY, handler: keyHandler),
+ commandCenter.playCommand: Config(key: MP_KEY_PLAYONLY, handler: keyHandler),
+ commandCenter.stopCommand: Config(key: MP_KEY_STOP, handler: keyHandler),
+ commandCenter.nextTrackCommand: Config(key: MP_KEY_NEXT, handler: keyHandler),
+ commandCenter.previousTrackCommand: Config(key: MP_KEY_PREV, handler: keyHandler),
+ commandCenter.togglePlayPauseCommand: Config(key: MP_KEY_PLAY, handler: keyHandler),
+ commandCenter.seekForwardCommand: Config(key: MP_KEY_FORWARD, type: .repeatable, handler: keyHandler),
+ commandCenter.seekBackwardCommand: Config(key: MP_KEY_REWIND, type: .repeatable, handler: keyHandler),
+ commandCenter.changePlaybackPositionCommand: Config(handler: seekHandler),
+ ]
+
+ disabledCommands = [
+ commandCenter.changePlaybackRateCommand,
+ commandCenter.changeRepeatModeCommand,
+ commandCenter.changeShuffleModeCommand,
+ commandCenter.skipForwardCommand,
+ commandCenter.skipBackwardCommand,
+ commandCenter.enableLanguageOptionCommand,
+ commandCenter.disableLanguageOptionCommand,
+ commandCenter.ratingCommand,
+ commandCenter.likeCommand,
+ commandCenter.dislikeCommand,
+ commandCenter.bookmarkCommand,
+ ]
+
+ cover = (NSApp as? Application)?.getMPVIcon() ?? cover
+
+ for cmd in disabledCommands {
+ cmd.isEnabled = false
+ }
+ }
+
+ @objc func start() {
+ for (cmd, config) in configs {
+ cmd.isEnabled = true
+ cmd.addTarget(handler: config.handler)
+ }
+
+ updateInfoCenter()
+
+ NotificationCenter.default.addObserver(
+ self,
+ selector: #selector(self.makeCurrent),
+ name: NSApplication.willBecomeActiveNotification,
+ object: nil
+ )
+ }
+
+ @objc func stop() {
+ for (cmd, _) in configs {
+ cmd.isEnabled = false
+ cmd.removeTarget(nil)
+ }
+
+ infoCenter.nowPlayingInfo = nil
+ infoCenter.playbackState = .unknown
+
+ NotificationCenter.default.removeObserver(
+ self,
+ name: NSApplication.willBecomeActiveNotification,
+ object: nil
+ )
+ }
+
+ @objc func makeCurrent(notification: NSNotification) {
+ infoCenter.playbackState = .paused
+ infoCenter.playbackState = .playing
+ updateInfoCenter()
+ }
+
+ func updateInfoCenter() {
+ infoCenter.playbackState = isPaused ? .paused : .playing
+ infoCenter.nowPlayingInfo = (infoCenter.nowPlayingInfo ?? [:]).merging([
+ MPNowPlayingInfoPropertyMediaType: NSNumber(value: MPNowPlayingInfoMediaType.video.rawValue),
+ MPNowPlayingInfoPropertyPlaybackProgress: NSNumber(value: 0.0),
+ MPNowPlayingInfoPropertyPlaybackRate: NSNumber(value: isPaused ? 0 : rate),
+ MPNowPlayingInfoPropertyElapsedPlaybackTime: NSNumber(value: position),
+ MPMediaItemPropertyPlaybackDuration: NSNumber(value: duration),
+ MPMediaItemPropertyTitle: title,
+ MPMediaItemPropertyArtist: artist ?? chapter ?? "",
+ MPMediaItemPropertyAlbumTitle: album ?? "",
+ MPMediaItemPropertyArtwork: MPMediaItemArtwork(boundsSize: cover.size) { _ in return self.cover }
+ ]) { (_, new) in new }
+ }
+
+ lazy var keyHandler: ConfigHandler = { event in
+ guard let config = self.configs[event.command] else {
+ return .commandFailed
+ }
+
+ var state = config.state
+ if config.type == .repeatable {
+ state = config.state == MP_KEY_STATE_DOWN ? MP_KEY_STATE_UP : MP_KEY_STATE_DOWN
+ self.configs[event.command]?.state = state
+ }
+
+ EventsResponder.sharedInstance().handleMPKey(config.key, withMask: Int32(state))
+
+ return .success
+ }
+
+ lazy var seekHandler: ConfigHandler = { event in
+ guard let posEvent = event as? MPChangePlaybackPositionCommandEvent else {
+ return .commandFailed
+ }
+
+ let success = String(format: "seek %.02f absolute", posEvent.positionTime).withCString {
+ EventsResponder.sharedInstance().queueCommand(UnsafeMutablePointer<Int8>(mutating: $0))
+ }
+
+ return success ? .success : .commandFailed
+ }
+
+ @objc func processEvent(_ event: UnsafeMutablePointer<mpv_event>) {
+ switch event.pointee.event_id {
+ case MPV_EVENT_PROPERTY_CHANGE:
+ handlePropertyChange(event)
+ default:
+ break
+ }
+ }
+
+ func handlePropertyChange(_ event: UnsafeMutablePointer<mpv_event>) {
+ let pData = OpaquePointer(event.pointee.data)
+ guard let property = UnsafePointer<mpv_event_property>(pData)?.pointee else {
+ return
+ }
+
+ switch String(cString: property.name) {
+ case "pause" where property.format == MPV_FORMAT_FLAG:
+ isPaused = LibmpvHelper.mpvFlagToBool(property.data) ?? false
+ case "time-pos" where property.format == MPV_FORMAT_DOUBLE:
+ let newPosition = max(LibmpvHelper.mpvDoubleToDouble(property.data) ?? 0, 0)
+ if Int((floor(newPosition) - floor(position)) / rate) != 0 {
+ position = newPosition
+ }
+ case "duration" where property.format == MPV_FORMAT_DOUBLE:
+ duration = LibmpvHelper.mpvDoubleToDouble(property.data) ?? 0
+ case "speed" where property.format == MPV_FORMAT_DOUBLE:
+ rate = LibmpvHelper.mpvDoubleToDouble(property.data) ?? 1
+ case "media-title" where [MPV_FORMAT_STRING, MPV_FORMAT_NONE].contains(property.format):
+ title = LibmpvHelper.mpvStringArrayToString(property.data) ?? ""
+ case "chapter-metadata/title" where [MPV_FORMAT_STRING, MPV_FORMAT_NONE].contains(property.format):
+ chapter = LibmpvHelper.mpvStringArrayToString(property.data)
+ case "metadata/by-key/album" where [MPV_FORMAT_STRING, MPV_FORMAT_NONE].contains(property.format):
+ album = LibmpvHelper.mpvStringArrayToString(property.data)
+ case "metadata/by-key/artist" where [MPV_FORMAT_STRING, MPV_FORMAT_NONE].contains(property.format):
+ artist = LibmpvHelper.mpvStringArrayToString(property.data)
+ default:
+ break
+ }
+ }
+}