Book

Exploring Freelancing

Navigate freelancing as a developer; find clients, manage contracts, ensure timely payment, and learn from experiences!


Exploring MusicKit

I wrote this book to fill in the gap for the documentation, filled with examples so that you do not have to spend time experimenting yourself.

I hope this book provides value to you and your app, and you enjoy working with MusicKit and integrating Apple Music into your app!

Use the discount code “early” for a massive 75% discount!

I want this!


I searched for questions to answer on Stack Overflow and stumbled upon this question on Cannot convert value of type PlayParameters? to expected argument type MPMusicPlayerPlayParameters.

While PlayParameters is a part of MusicKit for Swift, MPMusicPlayerPlayParameters belongs to MediaPlayer framework.

The author wants to use the parameter fetched from the latest API and use it to queue an application music player.

let player = MPMusicPlayerController.applicationMusicPlayer
let queue  = MPMusicPlayerPlayParametersQueueDescriptor(playParametersQueue: heavyRotation[album].playParameters)
player.prepareToPlay()
player.play()

They printed the data, and it returns:

Optional(MusicKit.PlayParameters(id: l.vvLd05C, kind: "album", dictionary: ["isLibrary": true (Bool), "kind": "album" (String), "id": "l.vvLd05C" (String)]))

While both are somewhat incompatible, you’ve to find a way to make them work together.

Being Wrong

As I was excited to answer this question, I told them a wrong solution by making assumptions about how PlayParameters works.

While MusicKit’s PlayParameters is a structure, MPMusicPlayerPlayParameters initializers expect a dictionary of [String: Any].

So, I answered as following:

let player = MPMusicPlayerController.applicationMusicPlayer

let parameters = heavyRotation[album].playParameters.dictionary
let queue = [MPMusicPlayerPlayParameters(parameters)]

let queue = MPMusicPlayerPlayParametersQueueDescriptor(playParametersQueue: queue)

player.prepareToPlay()
player.play()

But.

From the documentation, PlayParameters is an opaque object that represents parameters to initiate playback of a playable music item using a music player.

So, you cannot just directly use the dot syntax and access the dictionary value even if it prints in the console.

Investigation

And as expected, they responded that the playParameters have no value of dictionary.

I answered them wrongly.

I felt it was my duty to investigate further.

There were hours of back and forth while feeling helpless because of the lack of documentation.

Encoding and Decoding PlayParameters

At the end (with the help of my close friend), I realized that I could encode the value of structure and then decode it to MPMusicPlayerPlayParameters.

In the end, the underlying properties are still the same!

So, after testing The Weeknd’s Dawn FM album, here’s the solution:

do {
  /// Request and Response
  let request = MusicCatalogResourceRequest<Album>(matching: \.id, equalTo: "1603171516")
  let response = try await request.response()
  
  /// Fetching Album
  guard let album = response.items.first else { return }
  
  /// Encoding parameters
  let data = try JSONEncoder().encode(album.playParameters)
  
  /// Decoding Parameters to `MPMusicPlayerPlayParameters`
  let playParameters = try JSONDecoder().decode(MPMusicPlayerPlayParameters.self, from: data)
  
  /// Creating the Queue
  let queue = MPMusicPlayerPlayParametersQueueDescriptor(playParametersQueue: [playParameters])
  
  let player = MPMusicPlayerController.applicationMusicPlayer
  
  /// Setting the Queue
  player.setQueue(with: queue)
  try await player.prepareToPlay()
    
  /// Finally, playing the album!
  player.play()
} catch {
  print(error)
}

And it sets the queue!

Setting the Queue Directly

I don’t understand the use of play parameters in the first place. From what I’ve seen, you can directly set the queue if you’re using ApplicationMusicPlayer:

let request = MusicCatalogResourceRequest<Album>(matching: \.id, equalTo: "1603171516")
let response = try await request.response()

guard let album = response.items.first else { return }
  
let player = ApplicationMusicPlayer.shared

player.queue = [album] /// <- directly add the whole album to the queue

try await player.prepareToPlay()
try await player.play()

Conclusion

In this post, we learned about using PlayParameters and MPMusicPlayerPlayParameters.

While the author was looking for a way to play their local albums, this one works for Apple Music Catalog.

Every library album has a library ID instead of a normal ID. So, you cannot use it directly. As a workaround, I call another request to check if it exists on the catalog and then play it.

I’m still exploring how to use it in a better way because I’ve seen third-party Apple Music apps replicating it, so there’s something that I’m missing. A post for some other day!

Also, if you know how to do it, please let me know on Twitter! I hope you enjoyed reading!

Book

Exploring Freelancing

Navigate freelancing as a developer; find clients, manage contracts, ensure timely payment, and learn from experiences!