Why MusicKit, why?

Why MusicKit, why?
Photo by Michael Henry / Unsplash

I'm trying to rebuild my random song app - the one that I naively build for the 1000 Albums project. Since then, a lot has changed and it's possible to build both macOS and iOS apps in the same target in Xcode.

So I thought - why not? I'd like to make an app that automatically finds a random song and then plays it, like Southside of Heaven that I just found. Or Sors de ma tête.

Well that was a mistake. Little did I know how poor Apple's MusicKit APIs were. I took for granted that I would be able to actually see the song playback progress! Or know when a song finished playing!

I'm going to quickly explore the limitations of their code, and why I just can't build what I want to build.

Playback progress

You would think that knowing the playback progress of a song would be important, right? Like that little bar that shows you how much time you've listened for, and how much is left?

Well apparently Apple doesn't think it's important? Here's the rub:

  1. Apple provides playbackTime - which I can manually ask for, but isn't an observable object because apparently it would cause some issues with emitting too many events. Fair. Here's the recommendation - create a long-running animation that starts at playbackTime and matches the playbackRate and then reset it when playbackStatus changes.
  2. The structure of the ApplicationMusicPlayer.shared player (Apple can we please move away from singletons) has playbackTime as a direct property - but shared is not observable. Instead, the state property is observable so we can view playbackTime or playbackStatus, but if we want to get playbackTime, we need to have a dependency on the singleton object in addition to its state. 🤦‍♂️ I don't think Apple realizes how they're creating dependency nightmares with their APIs.
  3. It gets worse. ApplicationMusicPlayer.State isn't even equatable! So I can't do an onChange handler! It's like whoever wrote this framework didn't even bother to try building anything at all with it.
  4. When I try to manually observing the state changes, I still have to reach out into the shared object to get the state values. They're not even passed in the sink - because the only thing I can observe is the objectWillChange publisher.
  5. Then when I'm actually observing the changes, I get junk status updates. For example, I start playing and then I get STOPPED and PAUSED sent before PLAYING. Really? And then when the song ends I get a PLAYING event emitted right before a PAUSED event. And not even a STOPPED event. Why is this so broken?
  6. And because I can't use onChange, I need to make a separate property that wraps the playbackStatus so that I can get onChange events.
    @ObservedObject var state = ApplicationMusicPlayer.shared.state
    var playbackStatus: ApplicationMusicPlayer.PlaybackStatus {
        state.playbackStatus
    }

I mean, what a pain. And all of this just to try to begin getting the proper playbackTime animation for the song. The way Apple has built this API really doesn't promote modular code.

Finished Playing

Another headache dealing with the API is trying to figure out exactly when a song finished playing. Surely, you might think, that has to be an obvious feature of a music API. But no, Apple, in their infinite wisdom decided to omit it from MusicKit. This StackOverflow post has gone unanswered after 6 years. This Apple support thread was just a great example of the framework developers being defensive, and is actually a pretty fun read. The end result? Me posting to ask the framework dev if they'll ever implement the feature. I don't know how to tag, apparently though.

Conclusion

I still have a lot of work to get music tracks working seamlessly. macOS support differs from iOS support and the APIs are barely usable. Even the sample project is missing basic functionality.

Lesson learned? Next app I work on won't rely nearly as much on Apple's frameworks. What a pain.

Follow the discussion on Reddit!