Why MusicKit, why?
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:
- 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 atplaybackTime
and matches theplaybackRate
and then reset it whenplaybackStatus
changes. - The structure of the
ApplicationMusicPlayer.shared
player (Apple can we please move away from singletons) hasplaybackTime
as a direct property - butshared
is not observable. Instead, thestate
property is observable so we can viewplaybackTime
orplaybackStatus
, but if we want to getplaybackTime
, 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. - It gets worse.
ApplicationMusicPlayer.State
isn't evenequatable
! So I can't do anonChange
handler! It's like whoever wrote this framework didn't even bother to try building anything at all with it. - 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 theobjectWillChange
publisher. - Then when I'm actually observing the changes, I get junk status updates. For example, I start playing and then I get
STOPPED
andPAUSED
sent beforePLAYING
. Really? And then when the song ends I get aPLAYING
event emitted right before aPAUSED
event. And not even aSTOPPED
event. Why is this so broken? - And because I can't use
onChange
, I need to make a separate property that wraps theplaybackStatus
so that I can getonChange
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.