Using MusicKit to retrieve playlists and contents of playlists

What is the best approach to retrieve playlists using MusicKit. I can't seem to guess from the docs how to use the API to accomplish this, as well as retrieving tracks from a playlist.

I am in the process of migrating code from Apple Music API to MusicKit and I potentially could use MusicDataRequest to retrieve playlists using the Apple Music API endpoints, but I want to be sure there are no better alternatives.

Sample code would definitely help.

Answered by Frameworks Engineer in 702641022

Hello @ridmaur,

Given a Playlist identifier, you can create a MusicCatalogResourceRequest using the init(matching:equalTo:) initializer.

let playlistRequest = MusicCatalogResourceRequest<Playlist>(matching: \.id, equalTo: "pl.ba2404fbc4464b8ba2d60399189cf24e")
let playlistResponse = try await playlistRequest.response()

if let playlist = playlistResponse.items.first {
    print("\(playlist)")
} else {
    print("Couldn't find playlist.")
}

Here's the corresponding console output:

Playlist(id: "pl.ba2404fbc4464b8ba2d60399189cf24e", name: "Hits in Spatial Audio", curatorName: "Apple Music Hits")

Given this instance of Playlist, you can get another instance that includes more properties, such as .tracks:

let detailedPlaylist = try await playlist.with([.tracks])
let tracks = detailedPlaylist.tracks ?? []
print("\(tracks)")

Here's the corresponding (abbreviated) console output:

MusicItemCollection<Track>(
  items: [
    Track.song(Song(id: "1440776809", title: "Moment 4 Life (feat. Drake)", artistName: "Nicki Minaj & Drake")),
    Track.song(Song(id: "1603171970", title: "Less Than Zero", artistName: "The Weeknd")),
    Track.song(Song(id: "1604411600", title: "Iffy", artistName: "Chris Brown")),
    [...]
    Track.song(Song(id: "1363310495", title: "Wasted Times", artistName: "The Weeknd")),
    Track.song(Song(id: "1440907406", title: "MotorSport", artistName: "Migos, Nicki Minaj & Cardi B"))
  ],
  hasNextBatch: true
)

You can also achieve the same result more efficiently, in a way that only issues one network request, by passing additional properties to be loaded directly as the MusicCatalogResourceRequest's properties:

var playlistRequest = MusicCatalogResourceRequest<Playlist>(matching: \.id, equalTo: "pl.ba2404fbc4464b8ba2d60399189cf24e")
playlistRequest.properties = [.tracks]

let playlistResponse = try await playlistRequest.response()
if let playlist = playlistResponse.items.first {
    print("\(playlist)")
    
    let tracks = playlist.tracks ?? []
    print("\(tracks)")
} else {
    print("Couldn't find playlist.")
}

I hope this helps.

Best regards,

Accepted Answer

Hello @ridmaur,

Given a Playlist identifier, you can create a MusicCatalogResourceRequest using the init(matching:equalTo:) initializer.

let playlistRequest = MusicCatalogResourceRequest<Playlist>(matching: \.id, equalTo: "pl.ba2404fbc4464b8ba2d60399189cf24e")
let playlistResponse = try await playlistRequest.response()

if let playlist = playlistResponse.items.first {
    print("\(playlist)")
} else {
    print("Couldn't find playlist.")
}

Here's the corresponding console output:

Playlist(id: "pl.ba2404fbc4464b8ba2d60399189cf24e", name: "Hits in Spatial Audio", curatorName: "Apple Music Hits")

Given this instance of Playlist, you can get another instance that includes more properties, such as .tracks:

let detailedPlaylist = try await playlist.with([.tracks])
let tracks = detailedPlaylist.tracks ?? []
print("\(tracks)")

Here's the corresponding (abbreviated) console output:

MusicItemCollection<Track>(
  items: [
    Track.song(Song(id: "1440776809", title: "Moment 4 Life (feat. Drake)", artistName: "Nicki Minaj & Drake")),
    Track.song(Song(id: "1603171970", title: "Less Than Zero", artistName: "The Weeknd")),
    Track.song(Song(id: "1604411600", title: "Iffy", artistName: "Chris Brown")),
    [...]
    Track.song(Song(id: "1363310495", title: "Wasted Times", artistName: "The Weeknd")),
    Track.song(Song(id: "1440907406", title: "MotorSport", artistName: "Migos, Nicki Minaj & Cardi B"))
  ],
  hasNextBatch: true
)

You can also achieve the same result more efficiently, in a way that only issues one network request, by passing additional properties to be loaded directly as the MusicCatalogResourceRequest's properties:

var playlistRequest = MusicCatalogResourceRequest<Playlist>(matching: \.id, equalTo: "pl.ba2404fbc4464b8ba2d60399189cf24e")
playlistRequest.properties = [.tracks]

let playlistResponse = try await playlistRequest.response()
if let playlist = playlistResponse.items.first {
    print("\(playlist)")
    
    let tracks = playlist.tracks ?? []
    print("\(tracks)")
} else {
    print("Couldn't find playlist.")
}

I hope this helps.

Best regards,

Many thanks for this. Can you also help with how to get a list of library playlists for the user, like you can with the Apple Music API endpoint: https://api.music.apple.com/v1/me/library/playlists?

Hi again @ridmaur,

In iOS 15 and aligned Apple platform OS releases, MusicKit doesn't offer any structured request to access the user's library.

That said, you can use MusicKit's MusicDataRequest to load data from an arbitrary Apple Music API endpoint.

When you begin decoding the raw JSON output, make sure to leverage the fact that MusicKit's items and MusicItemCollection conform to Codable.

Please also refer to this related thread, which shows how to load LibraryArtists from Apple Music API. You can adapt this easily for Playlist.

I hope this helps.

Best regards,

Following up on this thread, I can successfully load playlists using MusicDataRequest. For example I can see playlist returned with and id of "p.aJe0ME5I3eZQ84d" from my Apple Music account.

Trying your code fetch the tracks of the playlist did not work out of the box. I had to modify it to

var playlistRequest = MusicCatalogResourceRequest<Playlist>(matching: \.id, equalTo: MusicItemID(playlistId)), i.e. wrapping the returned playlist id to a MusicItemID. Is this correct? Because using the id this way, it results in the following error:

[DataRequesting] Failed to perform MusicDataRequest.Context(

  url: https://api.music.apple.com/v1/catalog/nl/playlists/p.aJe0ME5I3eZQ84d?l=en-GB&include=tracks&omit%5Bresource%5D=autos,

  currentRetryCounts: [.other: 1]

) with MusicDataRequest.Error(

  status: 404,

  code: 40400,

  title: "Resource Not Found",

  detailText: "Resource with requested id was not found",

  id: "4B4ETVMCUXDRBAJ5OCLJLAKLSU",

  originalResponse: MusicDataResponse(

    data: 159 bytes,

    urlResponse: <NSHTTPURLResponse: 0x00000002829a78a0>

  )

).

What am I doing wrong?

Hello @ridmaur,

The results you are getting are expected.

Indeed, the playlists you've loaded using MusicDataRequest are in fact Apple Music API resources of type LibraryPlaylists.

However, MusicCatalogResourceRequest is focused on the catalog portion of Apple Music API, and as such, it cannot be used to fetch library resources.

It seems to me like you are decoding the raw JSON output in MusicDataResponse's data in a custom way, which isn't the recommendation I offered previously.

When you begin decoding the raw JSON output, make sure to leverage the fact that MusicKit's items and MusicItemCollection conform to Codable.

Please also refer to this related thread, which shows how to load LibraryArtists from Apple Music API. You can adapt this easily for Playlist.

Here's some sample code that does just that:

let libraryPlaylistsURL = URL(string: "https://api.music.apple.com/v1/me/library/playlists")!
let libraryPlaylistsRequest = MusicDataRequest(urlRequest: URLRequest(url: libraryPlaylistsURL))
let libraryPlaylistsResponse = try await libraryPlaylistsRequest.response()

let decoder = JSONDecoder()
let libraryPlaylists = try decoder.decode(MusicItemCollection<Playlist>.self, from: libraryPlaylistsResponse.data)

if let libraryPlaylist = libraryPlaylists.first {
    let detailedLibraryPlaylist = try await libraryPlaylist.with([.tracks])
    let tracks = detailedLibraryPlaylist.tracks ?? []
    print("\(tracks)")
}

I hope this helps.

Best regards,

Hello @ridmaur,

I just wanted to let you know you no longer need to use MusicDataRequest on iOS 16 beta 1 to fetch playlists from the user's library.

Instead, you can use the brand new MusicLibraryRequest in MusicKit.

Please check our new WWDC22 session video, Explore more content with MusicKit, which goes into this, and much more!

I hope you'll like it!

Best regards,

Using MusicKit to retrieve playlists and contents of playlists
 
 
Q