MusicKit - Access user's recently played music

I noticed that MusicKit updated so that you do not need to create any JWT tokens anymore (it's automatic).

But since it's automatic, how would I get the current user's recently played music?

I know you can do it with the API, but how would you do it with MusicKit (assuming you have MusicKit setup and running properly already)?

https://developer.apple.com/documentation/applemusicapi/get_recently_played_resources

I've been looking through the MusicKit documentation and can't find a single reference to it.

https://developer.apple.com/documentation/musickit

Hello @bigdaddy1,

In iOS 15 and aligned releases, MusicKit for Swift does include some interesting features focused around the Apple Music catalog, such as:

However, as explained in our WWDC session Meet MusicKit for Swift between the timestamps 7:47 and 9:46, it also includes a general purpose data request, MusicDataRequest, which can be used to load data from an arbitrary Apple Music API endpoint.

Of course, this can also be used for the endpoint to get recently played resources. Following the method explained in our WWDC session, you can define a value type that can represent all the different types of recently played resources, which are Album, Playlist and Station:

enum RecentlyPlayedItem {
    case album(Album)
    case playlist(Playlist)
    case station(Station)
}

Since we are about to use it in a MusicItemCollection, you should make it conform to the MusicItem protocol:

extension RecentlyPlayedItem: MusicItem {
    var id: MusicItemID {
        let id: MusicItemID
        switch self {
            case .album(let album):
                id = album.id
            case .playlist(let playlist):
                id = playlist.id
            case .station(let station):
                id = station.id
        }
        return id
    }
}

Lastly, we need to make this type conform to the Decodable protocol. The usual pattern to add Decodable conformance to such polymorphic types from Apple Music API requires a custom implementation, but you can still let MusicKit do the heavy lifting by deferring to the Decodable initializers of Album, Playlist and Station:

extension RecentlyPlayedItem: Decodable {
    private enum CodingKeys: CodingKey {
        case type
    }
    
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        let type = try values.decode(String.self, forKey: .type)
        switch type {
            case "albums", "library-albums":
                let album = try Album(from: decoder)
                self = .album(album)
            case "playlists", "library-playlists":
                let playlist = try Playlist(from: decoder)
                self = .playlist(playlist)
            case "stations":
                let station = try Station(from: decoder)
                self = .station(station)
            default:
                let decodingErrorContext = DecodingError.Context(
                    codingPath: decoder.codingPath, 
                    debugDescription: "Unexpected type \"\(type)\" encountered for RecentlyPlayedItem."
                )
                throw DecodingError.typeMismatch(RecentlyPlayedItem.self, decodingErrorContext)
        }
    }
}

Once you've defined this RecentlyPlayedItem value type that conforms to both MusicItem and Decodable, you can load recently played items and decode them very easily:

    let url = URL(string: "https://api.music.apple.com/v1/me/recent/played")!
    let request = MusicDataRequest(urlRequest: URLRequest(url: url))
    let response = try await request.response()
    let decoder = JSONDecoder()
    let recentlyPlayedItems = try decoder.decode(MusicItemCollection<RecentlyPlayedItem>.self, from: response.data)
    print("Recently played items: \(recentlyPlayedItems)")

Here's the sort of output I see when I run this code:

Recently played items: MusicItemCollection<RecentlyPlayedItem>(
  items: [
    playlist(Playlist(
      id: "pl.5ee8333dbe944d9f9151e97d92d1ead9",
      name: "A-List Pop",
      curatorName: "Apple Music Pop",
      isChart: "false",
      kind: "editorial",
      lastModifiedDate: "2021-12-10",
      shortDescription: "GAYLE spares no one on “abcdefu.”",
      standardDescription: "As its title might suggest, GAYLE’s “abcdefu” begins with a list: “F**k you, and your mom, and your sister, and your job,” she sings. “And your broke-*** car, and that shit you call art.” There’s plenty more where that came from. The headliner on A-List Pop this week, it’s a breakup anthem with bite, the Dallas-born, Nashville-based singer-songwriter laying waste to just about everyone and everything in her path, including a couch. “F**k you and your friends that I'll never see again,” she sings, lullaby-style. “Everybody but your dog, you can all f**k off.” Add A-List Pop to your library to stay up on the latest and greatest pop music."
    )),
    station(Station(
      id: "ra.978194965",
      name: "Apple Music 1",
      isLive: "true"
    )),
    album(Album(
      id: "560097651",
      title: "The Heist (Deluxe Edition)",
      artistName: "Macklemore & Ryan Lewis",
      contentRating: "explicit",
      copyright: ℗ 2012 Macklemore, LLC.,
      genreNames: [
        "Hip-Hop",
        "Music",
        "Hip-Hop/Rap"
      ],
      isCompilation: false,
      isComplete: true,
      isSingle: false,
      releaseDate: "2012-10-09",
      trackCount: 19,
      upc: "707541525299"
    )),
[...]
  ],
  hasNextBatch: true
)

I hope this helps.

Best regards,

How do I parse out the items from "Playlist"? I am trying to adapt this to pull recently played tracks (https://api.music.apple.com/v1/me/recent/played/tracks) and I can print out the list of songs, but I am trying to pull back the title, artistName and imageURL but not having any luck....

Hello @damartin1,

Actually, MusicKit on iOS 16 Beta includes new structured requests to fetch recently played items. With these, you no longer need to use MusicDataRequest.

Instead, you could achieve the same thing I previously showed by simply using MusicRecentlyPlayedContainerRequest:

    let request = MusicRecentlyPlayedContainerRequest()
    let response = try await request.response()
    print("Recently played items: \(response.items)")

If you just want recently played tracks, you can specify that in the generic parameter for MusicRecentlyPlayedRequest:

let request = MusicRecentlyPlayedRequest<Track>()
let response = try await request.response()
for track in response.items {
print("Recently played track titled '\(track.title)' from '\(track.artistName)'.")
}

As for the "imageURL", I recommend you just pass the artwork of a Track directly to ArtworkImage, which is a SwiftUI View that can render the Artwork very easily. Compared to the idea of getting a URL and loading the image data manually, using ArtworkImage is a more streamlined and future proof way of rendering music artwork.

I hope this helps.

Best regards,

MusicKit - Access user's recently played music
 
 
Q