Search code examples
objective-cswiftmacoscocoa

How to get currently playing song on Mac (Swift)


On Mac the touch bar can automatically detect what app is playing audio, and lets you play/pause, skip, and even seek through the audio. This works for Spotify, Quicktime, and even websites in your browser, like a YouTube tab on Google Chrome. How can I get the same information (song name, thumbnail, app that's playing the song, duration, etc.) by using Swift?


Solution

  • macOS has a bunch more system integrations with media players. For example you can ask Siri for the current song or use the Now Playing Today widget:

    All of these integrations use private APIs in the Media Remote private framework. Since it is a private framework Apple will reject your app if you try to submit it on the Mac App Store. You can still notarize it for distribution outside of the App Store.

    To use that framework you can try this:

    // 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)
    })