In the previous post, I introduced the concept of the Dynamic Notch. To demonstrate its potential, I've created a quick mock of a media player. Fast forward a few weeks, and I've been busy breathing life into this idea. What started as a mere UI mock has now transformed into a fully functional application.
The Notch player is a mini media player that works underneath MacBook's notch. Here is the quick demo:
When I started development, the main obstacle I had to overcome was to tap into currently played media. On iOS, there is a MediaPlayer
framework, but on macOS it doesn't work. Normally that would mean a dead end, and I would have to abandon the project. But there is a private framework named RemoteMedia
. And on macOS we are not restricted by the AppStore rules, so I could use the private API.
I've never used private APIs, and I thought it would be difficult, but it turned out it is not that hard. All I had to do is to create a pointer to the method I wanted to call. Here is an example of how I tapped into the MRMediaRemoteSendCommand(Int, AnyObject?)
method:
final class MediaRemoteProxy {
private static let mediaRemoteBundle = CFBundleCreate(kCFAllocatorDefault, NSURL(fileURLWithPath: "/System/Library/PrivateFrameworks/MediaRemote.framework"))
typealias MRMediaRemoteSendCommandFunction = @convention(c) (Int, AnyObject?) -> Bool
static func proxyMRMediaRemoteSendCommand() -> MRMediaRemoteSendCommandFunction? {
guard let pointer = CFBundleGetFunctionPointerForName(mediaRemoteBundle, "MRMediaRemoteSendCommand" as CFString) else {
return nil
}
return unsafeBitCast(pointer, to: MRMediaRemoteSendCommandFunction.self)
}
}
Now, with such a proxy, I can send commands to MediaRemote
:
func play() {
let MRMediaRemoteSendCommand = MediaRemoteProxy.proxyMRMediaRemoteSendCommand()
_ = MRMediaRemoteSendCommand?(MediaRemoteCommand.play.rawValue, nil)
}
Once I tapped into the MediaRemote
framework, I had everything to finish the Notch Player. The MediaRemote
framework:
- Provides information about currently played media,
- Returns cover image,
- Knows about the current state of media playback,
- Allows to control the media playback.
The MediaRemote
framework has everything I needed, yet it's not perfect. Often it loses track of whether the media is played. Other times, it doesn't know what the current playback time is.
At first, I thought my proxy was at fault, but I've noticed the same issues with the "Now playing" widget built into macOS.
Although the NotchPlayer is not perfect, I'm happy with the final product. It works as well as the application provided by Apple. Because of the private API, I cannot release it on the AppStore, but I might do that on Gumroad or a similar service.
Comments
Anything interesting to share? Write a comment.