Post not yet marked as solved
Post marked as unsolved with 8 replies, 2,075 views
Hi there,
tl;dr: What's the best way to get all tracks (with catalog IDs) from a playlist that has more than 100 tracks, using MusicLibraryRequest.
I'm doing something dumb, not understanding something, and possibly both.
I've got an existing, kinda okay function that uses the MusicDataRequest and the Apple Music API to fetch all tracks from a playlist, with pagination like this:
func getTracksFromAppleMusicLibraryPlaylist(playlist: AppleMusicPlaylist) async throws -> [MusicKit.Track]? {
var tracksToReturn: [MusicKit.Track] = []
var libraryTracks: [AppleMusicLibraryTrack] = []
@Sendable
func fetchTracks(playlist: AppleMusicPlaylist, offset: Int) async -> AppleMusicPlaylistFetchResponse? {
do {
let playlistId = playlist.id
var playlistRequestURLComponents = URLComponents()
playlistRequestURLComponents.scheme = "https"
playlistRequestURLComponents.host = "api.music.apple.com"
playlistRequestURLComponents.path = "/v1/me/library/playlists/\(playlistId)/tracks"
playlistRequestURLComponents.queryItems = [
URLQueryItem(name: "include", value: "catalog"),
URLQueryItem(name: "limit", value: "100"),
URLQueryItem(name: "offset", value: String(offset)),
]
if let playlistRequestURL = playlistRequestURLComponents.url {
let playlistRequest = MusicDataRequest(urlRequest: URLRequest(url: playlistRequestURL))
let playlistResponse = try await playlistRequest.response()
let decoder = JSONDecoder()
// print("Get Tracks Dump")
// print(String(data: playlistResponse.data, encoding: .utf8)!)
let response = try decoder.decode(AppleMusicPlaylistFetchResponse.self, from: playlistResponse.data)
return response
} else {
print("Bad URL!")
}
} catch {
print(error)
}
return nil
}
Logger.log(.info, "Fetching inital tracks from \(playlist.attributes.name)")
if let response = await fetchTracks(playlist: playlist, offset: 0) {
if let items = response.data {
libraryTracks = items
}
if let totalItemCount = response.meta?.total {
Logger.log(.info, "There are \(totalItemCount) track(s) in \(playlist.attributes.name)")
if totalItemCount > 100 {
let remainingItems = (totalItemCount - 100)
let calls = remainingItems <= 100 ? 1 : (totalItemCount - 100) / 100
Logger.log(.info, "Total items: \(totalItemCount)")
Logger.log(.info, "Remaining items: \(remainingItems)")
Logger.log(.info, "Calls: \(calls)")
await withTaskGroup(of: [AppleMusicLibraryTrack]?.self) { group in
for offset in stride(from: 100, to: calls * 100, by: 100) {
Logger.log(.info, "Fetching additional tracks from \(playlist.attributes.name) with offset of \(offset)")
group.addTask {
if let response = await fetchTracks(playlist: playlist, offset: offset) {
if let items = response.data {
return items
}
}
return nil
}
}
for await (fetchedTracks) in group {
if let tracks = fetchedTracks {
libraryTracks.append(contentsOf: tracks)
}
}
}
}
}
}
// props to @JoeKun for this bit of magic
Logger.log(.info, "Matching library playlist tracks with catalog tracks...")
for (i, track) in libraryTracks.enumerated() {
if let correspondingCatalogTrack = track.relationships?.catalog?.first {
tracksToReturn.append(correspondingCatalogTrack)
print("\(i) => \(track.id) corresponds to catalog track with ID: \(correspondingCatalogTrack.id).")
} else {
Logger.log(.warning, "\(i) => \(track.id) doesn't have any corresponding catalog track.")
}
}
if tracksToReturn.count == 0 {
return nil
}
return tracksToReturn
}
While not the most elegant, it gets the job done, and it's kinda quick due to the use of withTaskGroup .esp with playlists containing more than 100 songs/tracks.
Regardless, I'm kinda stuck, trying to do something similar with the new MusicLibraryReqeust in iOS 16.
The only way I can think of to get tracks from a playlist, using MusicLibraryRequest, having read the new docs, is like this:
@available(iOS 16.0, *)
func getAllTracksFromHugePlaylist(id: MusicItemID) async throws -> [MusicKit.Track]? {
do {
var request = MusicLibraryRequest<MusicKit.Playlist>()
request.filter(matching: \.id, equalTo: id)
let response = try await request.response()
if response.items.count > 0 {
if let tracks = try await response.items[0].with(.tracks, preferredSource: .catalog).tracks {
Logger.log(.info, "Playlist track count: \(tracks.count)")
return tracks.compactMap{$0}
}
}
} catch {
Logger.log(.error, "Could not: \(error)")
}
return nil
}
The problem with this is that .with seems to be capped at 100 songs/tracks, and I can't see any way to change that.
Knowing that, I can't seem to tell MusicLibraryRequest that I want the tracks of the playlist with the initial request, where before I could use request.properties = .tracks, which I could then paginate if available.
Any help setting me on the right course would be greatly appreciated.