【问题标题】:How to get currently playing song on Mac (Swift)如何在 Mac (Swift) 上获取当前正在播放的歌曲
【发布时间】:2020-04-03 00:34:54
【问题描述】:

在 Mac 上,触控栏可以自动检测正在播放音频的应用程序,并让您播放/暂停、跳过甚至搜索音频。这适用于 Spotify、Quicktime 甚至浏览器中的网站,例如 Google Chrome 上的 YouTube 标签。如何使用 Swift 获取相同的信息(歌曲名称、缩略图、正在播放歌曲的应用程序、时长等)?

【问题讨论】:

    标签: objective-c swift macos cocoa


    【解决方案1】:

    macOS 与媒体播放器有更多的系统集成。例如,您可以向 Siri 询问当前歌曲或使用“今日正在播放”小部件:

    所有这些集成都使用 Media Remote 私有框架中的私有 API。由于它是一个私有框架,如果您尝试在 Mac App Store 上提交它,Apple 将拒绝您的应用程序。您仍然可以对其进行公证,以便在 App Store 之外分发。

    要使用该框架,您可以尝试以下操作:

    // Load framework
    let bundle = CFBundleCreate(kCFAllocatorDefault, NSURL(fileURLWithPath: "/System/Library/PrivateFrameworks/MediaRemote.framework"))
    
    // Get a Swift function for MRMediaRemoteGetNowPlayingInfo
    guard let MRMediaRemoteGetNowPlayingInfoPointer = CFBundleGetFunctionPointerForName(bundle, "MRMediaRemoteGetNowPlayingInfo" as CFString) else { return }
    typealias MRMediaRemoteGetNowPlayingInfoFunction = @convention(c) (DispatchQueue, @escaping ([String: Any]) -> Void) -> Void
    let MRMediaRemoteGetNowPlayingInfo = unsafeBitCast(MRMediaRemoteGetNowPlayingInfoPointer, to: MRMediaRemoteGetNowPlayingInfoFunction.self)
    
    // Get a Swift function for MRNowPlayingClientGetBundleIdentifier
    guard let MRNowPlayingClientGetBundleIdentifierPointer = CFBundleGetFunctionPointerForName(bundle, "MRNowPlayingClientGetBundleIdentifier" as CFString) else { return }
    typealias MRNowPlayingClientGetBundleIdentifierFunction = @convention(c) (AnyObject?) -> String
    let MRNowPlayingClientGetBundleIdentifier = unsafeBitCast(MRNowPlayingClientGetBundleIdentifierPointer, to: MRNowPlayingClientGetBundleIdentifierFunction.self)
    
    // Get song info
    MRMediaRemoteGetNowPlayingInfo(DispatchQueue.main, { (information) in
        NSLog("%@", information["kMRMediaRemoteNowPlayingInfoArtist"] as! String)
        NSLog("%@", information["kMRMediaRemoteNowPlayingInfoTitle"] as! String)
        NSLog("%@", information["kMRMediaRemoteNowPlayingInfoAlbum"] as! String)
        NSLog("%@", information["kMRMediaRemoteNowPlayingInfoDuration"] as! String)
        let artwork = NSImage(data: information["kMRMediaRemoteNowPlayingInfoArtworkData"] as! Data)
    
        // Get bundle identifier
        let _MRNowPlayingClientProtobuf: AnyClass? = NSClassFromString("_MRNowPlayingClientProtobuf")
        let handle : UnsafeMutableRawPointer! = dlopen("/usr/lib/libobjc.A.dylib", RTLD_NOW)
        let object = unsafeBitCast(dlsym(handle, "objc_msgSend"), to:(@convention(c)(AnyClass?,Selector?)->AnyObject).self)(_MRNowPlayingClientProtobuf,Selector("a"+"lloc"))
        unsafeBitCast(dlsym(handle, "objc_msgSend"), to:(@convention(c)(AnyObject?,Selector?,Any?)->Void).self)(object,Selector("i"+"nitWithData:"),information["kMRMediaRemoteNowPlayingInfoClientPropertiesData"] as AnyObject?)
        NSLog("%@", MRNowPlayingClientGetBundleIdentifier(object))
        dlclose(handle)
    })
    

    【讨论】:

    • 当,这正是我要找的,非常感谢!我在互联网上的其他任何地方都找不到这个。
    • 还有一个问题 - 有没有办法订阅每次当前播放的媒体更改时触发的通知?这样我可以自动更新我的 UI。
    • @Xenon 您需要调用MRMediaRemoteRegisterForNowPlayingNotifications 类型为@convention(c) (DispatchQueue) -> Void,然后使用NSNotificationCenter 订阅kMRMediaRemoteNowPlayingInfoDidChangeNotification 通知
    • 非常感谢!我在哪里可以找到有关私有 API 和 MRMediaRemote 的更多信息?还有很多事情我想知道如何做,比如播放/暂停和寻找时间。
    • 如果你能读懂 Objective-C 代码,那么一个地方就是我正在开发的应用程序之一:github.com/dimitarnestorov/MusicBar 你会发现我是如何使用 Media Remote 发送命令的。有一个名为 class-dump 的工具可以从框架中转储头文件,但它只是 Objective-C 类(没有像我的答案中那样的 C 函数)。这个 repo 包含几乎所有最新版本的转储:github.com/w0lfschild/macOS_headers
    猜你喜欢
    • 2023-02-07
    • 1970-01-01
    • 2017-07-25
    • 2011-04-10
    • 2021-05-25
    • 2020-10-14
    • 2015-05-17
    • 2012-08-10
    • 1970-01-01
    相关资源
    最近更新 更多