Non-Apple Playlists in MusicKit

Hi,

I'm sorry if this is something I should know, but if I make a music catalog search such as:

let request 
   = MusicCatalogSearchRequest(term: searchTerm,
                               types: [Playlist.self])

I get the Apple Music playlists containing the searchTerm (say Rock, Rap, Jazz,...)

How do I get the non Apple playlists say the ones curated by Blue Note?

Thanks,

Daniel

Replies

Hello @dimsumthinking,

As I understand it, you are looking for the playlists associated with the curator named "Blue Note".

In iOS 15, MusicKit for Swift doesn't support curators as a regular MusicItem in Swift. But you can still get this information with MusicDataRequest.

First, define a custom type for curators:

struct MyCurator: MusicItem, Codable {
    struct Attributes: Codable {
        let artwork: Artwork?
        let name: String
        let url: URL?
    }

    let id: MusicItemID
    let attributes: Attributes
}

Technically, for the purpose of this question, you only need the id, and you don't need any attributes. But I figured I'd show you that you can very easily also include attributes, including complex ones like artwork.

Next, define a structure for the catalog search response including curators:

struct MyCuratorCatalogSearchResponse: Codable {
    struct Results: Codable {
        let curators: MusicItemCollection<MyCurator>?
    }

    let results: Results
}

Next, build a URL for the catalog search endpoint to search the specific curator you're interested in:

let countryCode = try await MusicDataRequest.currentCountryCode
    
var curatorSearchURLComponents = URLComponents()
curatorSearchURLComponents.scheme = "https"
curatorSearchURLComponents.host = "api.music.apple.com"
curatorSearchURLComponents.path = "/v1/catalog/\(countryCode)/search"
curatorSearchURLComponents.queryItems = [
    URLQueryItem(name: "term", value: "blue note"), 
    URLQueryItem(name: "types", value: "curators"), 
]
let curatorSearchURL = curatorSearchURLComponents.url!

Next, load the response from Apple Music API and find the first item in the curators collection:

let curatorSearchDataRequest = MusicDataRequest(urlRequest: URLRequest(url: curatorSearchURL))
let curatorSearchDataResponse = try await curatorSearchDataRequest.response()

let decoder = JSONDecoder()
let curatorSearchResponse = try decoder.decode(MyCuratorCatalogSearchResponse.self, from: curatorSearchDataResponse.data)

if let curator = curatorSearchResponse.results.curators?.first {
    […]
}

Finally, load the playlists relationship for this curator in the if block:

    var curatorPlaylistsURLComponents = URLComponents()
    curatorPlaylistsURLComponents.scheme = curatorSearchURLComponents.scheme
    curatorPlaylistsURLComponents.host = curatorSearchURLComponents.host
    curatorPlaylistsURLComponents.path = "/v1/catalog/\(countryCode)/curators/\(curator.id.rawValue)/playlists"
    let curatorPlaylistsURL = curatorPlaylistsURLComponents.url!
    
    let curatorPlaylistsDataRequest = MusicDataRequest(urlRequest: URLRequest(url: curatorPlaylistsURL))
    let curatorPlaylistsDataResponse = try await curatorPlaylistsDataRequest.response()
    
    let curatorPlaylists = try decoder.decode(MusicItemCollection<Playlist>.self, from: curatorPlaylistsDataResponse.data)
    print("\(curatorPlaylists)")

And here's the output for this:

MusicItemCollection<Playlist>(
  items: [
    Playlist(id: "pl.4e3d25ede18c4cd88f1ee51a3a608860", name: "Blue Note: The Finest In Jazz Since 1939", curatorName: "Blue Note Records"),
    Playlist(id: "pl.5661da036f0443b79fcb91f795461076", name: "Blue Note: Classic Hits", curatorName: "Blue Note Records"),
    Playlist(id: "pl.e4eeb129fb944566bcee6f9a2dc85e87", name: "The Blue Note Monthly", curatorName: "Blue Note Records"),
    Playlist(id: "pl.d4b0f96695464be78185c33c59e6c60b", name: "Grant Green: The Finest", curatorName: "Blue Note Records"),
    Playlist(id: "pl.ae68cdc95b994fde8009d0bb3ea07fa2", name: "Calm", curatorName: "Blue Note Records"),
    Playlist(id: "pl.ad678216ef504fabb74ec33c76dcefff", name: "Jazz Now!", curatorName: "Blue Note Records"),
    Playlist(id: "pl.f1e8dc8bbbaf40c3ba47e88edf120243", name: "Soul Jazz", curatorName: "Blue Note Records"),
    Playlist(id: "pl.26d4aa9540f84e849630a41274ea8a0a", name: "Blue Note Live!", curatorName: "Blue Note Records"),
    Playlist(id: "pl.f52ff4dd03bd49eb89c745a4f140b38a", name: "Blue Note Re:imagined", curatorName: "Blue Note Records"),
    Playlist(id: "pl.34f6399ab41941dfb88c776f473008c4", name: "Donald Byrd: The Finest", curatorName: "Blue Note Records")
  ],
  hasNextBatch: true
)

Also, please note raw server responses from Apple Music API often use a structure called a "resource collection"; and here, for any part of the responses parsed in the code listed above that corresponded to such a resource collection, I used MusicKit's MusicItemCollection. Not only does this mean you have fewer custom Codable data structures to write, but you also get very easy access to the next batch of items, if any, as shown above with the hasNextBatch: true line of the above output.

Hence, you can even do the following:

    if curatorPlaylists.hasNextBatch, let nextBatchOfCuratorPlaylists = try await curatorPlaylists.nextBatch() {
        print("\(nextBatchOfCuratorPlaylists)")
    }

which will produce the following output:

MusicItemCollection<Playlist>(
  items: [
    Playlist(id: "pl.fbc158ab86d04b31bf8ae4190c8d3876", name: "McCoy Tyner: The Finest", curatorName: "Blue Note Records"),
    Playlist(id: "pl.fd067778d800421cbddf786656a4830b", name: "Most Sampled", curatorName: "Blue Note Records"),
    Playlist(id: "pl.4c88e23eb9cf4d59a43237cf4d8b046f", name: "Hard Bop", curatorName: "Blue Note Records"),
    Playlist(id: "pl.facaf42f9b534f5aa376b23ab7ac54fd", name: "Art Blakey: The Finest", curatorName: "Blue Note Records"),
    Playlist(id: "pl.9476e3f54f3d4b1a82dcbeae98097bfe", name: "Post-Bop", curatorName: "Blue Note Records"),
    Playlist(id: "pl.27591bd727064012b5e8f45253020b6e", name: "Joe Henderson: The Finest", curatorName: "Blue Note Records"),
    Playlist(id: "pl.0b1d1d87a7664d859bfcb0b6d17ad30b", name: "Herbie Hancock: The Finest", curatorName: "Blue Note Records"),
    Playlist(id: "pl.d7f6fb544a7047ac83e63311d78905a6", name: "Blue Groove", curatorName: "Blue Note Records"),
    Playlist(id: "pl.e8905ab964034cae88e18f6719f1f3ed", name: "Lee Morgan: The Finest", curatorName: "Blue Note Records"),
    Playlist(id: "pl.84499d12b2e548599b3e81d1a467605d", name: "Classic Ballads", curatorName: "Blue Note Records")
  ],
  hasNextBatch: true
)

I hope this helps.

Best regards,

Thank you but that's not what I'm asking - perhaps I asked it badly.

When I make a request for playlists with the term "Jazz" I would like to get back playlists from all curators not just Apple Music. This would happen to include Blue Note.

The point is I wouldn't know ahead of time that Blue Note is a curator - I would like more discoverability in the APIs

I could find the categories available with one query and use a returned value such as Jazz to query all playlists or curators.

In fact I'd like more - but that would be a great start,

Best,

Daniel