Playing Songs and Albums with MusicKit (Bug?)

Hi,

I am trying to use MusicKit for playing a song or an album. I am using the following code for it:

Tested on iPhone 11 Pro, iOS 15.0.1

@MainActor
private func play<I: PlayableMusicItem>(_ item: I) async throws {

   let systemPlayer = SystemMusicPlayer.shared
   if !systemPlayer.isPreparedToPlay {
      try await systemPlayer.prepareToPlay()
   }

   let queue = systemPlayer.queue
   try await queue.insert(item, position: .afterCurrentEntry)
   try await systemPlayer.play()

}

Before I was using the "oldschool" way using the MusicPlayer framework as follows:

@MainActor
private func playOldschool(identifier: String) {
   let systemPlayer = MPMusicPlayerController.systemMusicPlayer

   if !systemPlayer.isPreparedToPlay {
      systemPlayer.prepareToPlay()
   }
   systemPlayer.setQueue(with: [identifier])
   systemPlayer.play()
}

Both have been tested under the same conditions (permissions, same MusicSubscription), however the one using MusicKit does not seem to work well as try await systemPlayer.prepareToPlay() fails. If I remove the prepareForPlay code, it fails on play() with the same message and error as prepareForPlay.

The logs show this:

[SDKPlayback] prepareToPlay failed [no target descriptor]
Error Domain=MPMusicPlayerControllerErrorDomain Code=1 "(null)"

I could not find anything for that error domain and code 1, however prepareToPlay fails even in playOldschool, if I use the async variant of the function.

At the moment I am staying with playOldschool, because that actually plays music.

I wonder if I should file a radar for this or if there is any additional requirement for MusicKit that I haven't fulfilled causing it to fail.

Any help is appreciated!

Accepted Reply

Hello @sharedRoutine,

Thanks for your question about MusicKit's SystemMusicPlayer.

Before you can call either prepareToPlay() or play(), you must first set the queue. Setting the queue can be achieved just by using the regular setter syntax in Swift.

What you're doing there by using insert_:position:) is not an operation that sets the queue; instead, it's an operation that mutates the queue that was supposed to have already been set.

So the right way to do this with MusicKit is as follows:

@MainActor
private func play<I: PlayableMusicItem>(_ item: I) async throws {
    let systemPlayer = SystemMusicPlayer.shared
    systemPlayer.queue = [item]
    try await systemPlayer.play()
}

Please note that for a use-case as simple as yours, there is no need to call prepareToPlay(); indeed, because we were able to make play() into an async method, it's designed to call prepareToPlay() on your behalf in a way that doesn't block the main thread.

I hope this helps.

Best regards,

Replies

Hello @sharedRoutine,

Thanks for your question about MusicKit's SystemMusicPlayer.

Before you can call either prepareToPlay() or play(), you must first set the queue. Setting the queue can be achieved just by using the regular setter syntax in Swift.

What you're doing there by using insert_:position:) is not an operation that sets the queue; instead, it's an operation that mutates the queue that was supposed to have already been set.

So the right way to do this with MusicKit is as follows:

@MainActor
private func play<I: PlayableMusicItem>(_ item: I) async throws {
    let systemPlayer = SystemMusicPlayer.shared
    systemPlayer.queue = [item]
    try await systemPlayer.play()
}

Please note that for a use-case as simple as yours, there is no need to call prepareToPlay(); indeed, because we were able to make play() into an async method, it's designed to call prepareToPlay() on your behalf in a way that doesn't block the main thread.

I hope this helps.

Best regards,