I'm trying to do something that I though would be simple; however, I'm tripping up on something...
In brief, I want to get all tracks from a playlist in an AppleMusic authorized user's playlist with the following function:
func getTracksFromAppleMusicPlaylist(playlistId: MusicItemID) async throws -> [Track]? {
print("Fetching AppleMusic Playlists...")
print("Playlist ID: \(playlistId)")
var playlistTracks: [Track]? = []
do {
var playlistRequest = MusicCatalogResourceRequest<Playlist>(matching: \.id, equalTo: playlistId )
playlistRequest.properties = [.tracks]
let playlistResponse = try await playlistRequest.response()
print("Playlist Response: \(playlistResponse)")
let playlistWithTracks = playlistResponse.items
let tracks = playlistWithTracks.flatMap { playlist -> MusicItemCollection<Track> in
playlist.tracks ?? []
}
playlistTracks = tracks.count > 1 ? tracks : nil
} catch {
print("Error", error)
// handle error
}
return playlistTracks
}
This function results in the following error:
2021-08-28 04:25:14.335455+0700 App[90763:6707890] [DataRequesting] Failed to perform MusicDataRequest.Context(
url: https://api.music.apple.com/v1/catalog/us/playlists/p.7XxxsXxXXxX?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: "QMK7GH4U7ITPMUTTBKIOMXXXX",
originalResponse: MusicDataResponse(
data: 159 bytes,
urlResponse: <NSHTTPURLResponse: 0x00000002820c0b60>
)
).
The playlistID being used is a value that has been picked from an existing playlist in the user's library, via a function that uses this code snippet:
if let url = URL(string: "https://api.music.apple.com/v1/me/library/playlists?limit=100") {
let dataRequest = MusicDataRequest(urlRequest: URLRequest(url: url))
...
The only thing I can think of is that the playlistId
from the above snippet is converted to a string when decoding into a struct, after which it is changed back to a MusicItemID
with an init, like MusicItemID(playlistId)
.
Any thoughts? Because I'm at a loss...
Hello @Kimfucious,
Indeed, identifiers of tracks or songs from the catalog are in a different space than identifiers of tracks or songs from the library.
In Apple Music API, there are actually different types of objects for catalog and library resources. For example, there are Songs which are used for songs in the catalog, and LibrarySongs.
One thing you could do is to leverage the catalog relationship of Apple Music API's LibrarySongs.
To do that, you can define the following structure:
struct MyLibraryTrack: MusicItem, Codable {
struct Relationships: Codable {
let catalog: MusicItemCollection<Track>?
}
let id: MusicItemID
let relationships: Relationships?
}
And then use it in conjunction with a MusicDataRequest to load the playlist tracks including the catalog
relationship:
var playlistTracksRequestURLComponents = URLComponents()
playlistTracksRequestURLComponents.scheme = "https"
playlistTracksRequestURLComponents.host = "api.music.apple.com"
playlistTracksRequestURLComponents.path = "/v1/me/library/playlists/\(playlistId.rawValue)/tracks"
playlistTracksRequestURLComponents.queryItems = [
URLQueryItem(name: "include", value: "catalog"),
]
let playlistTracksRequestURL = playlistTracksRequestURLComponents.url!
let playlistTracksRequest = MusicDataRequest(urlRequest: URLRequest(url: playlistTracksRequestURL))
let playlistTracksResponse = try await playlistTracksRequest.response()
let decoder = JSONDecoder()
let playlistTracks = try decoder.decode(MusicItemCollection<MyLibraryTrack>.self, from: playlistTracksResponse.data)
print("Playlist with ID \(playlistId) has tracks:")
for (i, track) in playlistTracks.enumerated() {
if let correspondingCatalogTrack = track.relationships?.catalog?.first {
print(" \(i) => \(track.id) corresponds to catalog track with ID: \(correspondingCatalogTrack.id).")
}
else {
print(" \(i) => \(track.id) doesn't have any corresponding catalog track.")
}
}
When testing this code with a sample playlist, I see the following:
Playlist with ID p.pkgS2BZZpN has tracks:
0 => i.xVexf4lLLXO corresponds to catalog song with ID: 320697087.
1 => i.dZext6kddXM doesn't have any corresponding catalog song.
2 => i.vv59U9P6641 corresponds to catalog song with ID: 632895520.
3 => i.rxJZfD2001x corresponds to catalog song with ID: 592365007.
4 => i.dlJzf6kddXM corresponds to catalog song with ID: 697195787.
5 => i.79eOiVJPPZW corresponds to catalog song with ID: 1190935915.
6 => i.E9rMIzOYYPl doesn't have any corresponding catalog song.
7 => i.B9paue544zP corresponds to catalog song with ID: 327818744.
8 => i.0qW2HV2773X corresponds to catalog song with ID: 736211119.
9 => i.dOBxS6kddXM doesn't have any corresponding catalog song.
10 => i.NaYmCP0VVdO doesn't have any corresponding catalog song.
11 => i.9algtNZ88R3 corresponds to catalog song with ID: 1451768057.
12 => i.99mmSNZ88R3 corresponds to catalog song with ID: 1140562212.
I believe this will unblock you to implement the logic you've been trying to implement lately.
I hope this helps.
Best regards,