Posts

Post not yet marked as solved
6 Replies
0 Views
Hi @CPDigitalDarkroom, The trick to using .edit, is that you need provide an array (i.e. sequence) of .items. In the below, playlist is the actual playlist retrieved using MusicLibraryRequest, and items is an array/sequence of tracks. With .edit, items replaces all tracks in the playlist, so you need to be sure you're providing what you want to be in the playlist with items. let updatedPlaylist = try await MusicLibrary.shared.edit(playlist, items: items) If all tracks are being removed from your playlist, using .edit, I would check to make sure that you are supplying and actual array of items. If that's empty, then you'll wind up with an empty playlist. I hope that makes sense. If not, feel free to ask, and I'll do my best to help you out.
Post not yet marked as solved
7 Replies
0 Views
HI @david-apple, Thanks for the pro advice! In my initial post on this thread, I mentioned getting all tracks from a playlist with over 100 tracks. Sorry, I know that post was a bit wordy, so it's easy to overlook that point. My findings are that .with, in this scenario, has a cap of 100 tracks, which is why I started looking at ways to get more than 100. This is an edge case, but I do need to accommodate for this scenario. I found that .nextBatch() allows for a limit of 300, which is nice and results in less calls. If I'm mistaken, and that is entirely possible, kindly advise. PS: Kindly note that using .preferredSource: .library or omitting that parameter results in the .edit method failing, as I've mentioned here. I'm loving the new iOS 16 stuff for MusicKit and hope my inputs help.
Post not yet marked as solved
7 Replies
0 Views
Thanks for the feedback, @JoeKun! I always appreciate your help 😊 My usage for this is not for displaying in the UI, but for various other things. For example, if I want to use the following to update the tracks in a playlist, where items is all existing tracks in the playlist plus some new tracks or minus a specific subset. let updatedPlaylist = try await MusicLibrary.shared.edit(playlist, items: items) At present, I don't see a way to use the .edit method otherwise. I think I need all those items ahead of time. Adding tracks could be done without the use of .edit, using a loop and multiple .adds; it's the removal of tracks that seems tricky to me. Of course, I could be missing something. If that's the case, I'd love to hear a better way to achieve this.
Post not yet marked as solved
7 Replies
0 Views
Hi @talkingsmall, Thanks much for your thorough and thoughtful reply. I was unaware (or blanking on) of the .hasNextBatch on MusicItemCollection for some reason. I appreciate you bringing that to my attention, as it helped me come up with a solution (extra logging added for clarity): @available(iOS 16.0, *) func getAllTracksFromPlaylistId(id: MusicItemID) async throws -> [Track]? {     func getAllTracks(tracks: MusicItemCollection<Track>) async throws -> [Track]? {         var hasNextBatch = true         var tracksToReturn = tracks.compactMap{$0}         var currentTrackCollection = tracks         Logger.log(.info, "Initial track count: \(tracks.count)")         do {             while hasNextBatch == true {                 if let nextBatchCollection = try await currentTrackCollection.nextBatch(limit: 300) { // 300 is max here tracksToReturn = tracksToReturn + nextBatchCollection.compactMap{$0}                     Logger.log(.info, "Fetched \(nextBatchCollection.count) track(s) from next batch")                     Logger.log(.info, "Current track subtotal: \(tracksToReturn.count)")                     if nextBatchCollection.hasNextBatch {                         Logger.log(.info, "nextBatchCollection has nextBatch.  Continuing while loop...")                         currentTrackCollection = nextBatchCollection                     } else {                         Logger.log(.info, "nextBatchCollection has no nextBatch. Breaking from while loop")                         hasNextBatch = false                     }                 } else {                     Logger.log(.info, "No results from nextBatch()! Breaking from while loop")                     hasNextBatch = false                 }             }             if tracksToReturn.count > 0 {                 Logger.log(.info, "Returning \(tracksToReturn.count) track(s)")                 return tracksToReturn             } else {                 Logger.log(.info, "tracksToReturn is empty!")                 return nil             }         } catch {             Logger.log(.error, "Could not get next batches!")         }         return nil     }     do {         var request = MusicLibraryRequest<MusicKit.Playlist>()         request.filter(matching: \.id, equalTo: id)         let response = try await request.response()         if let playlist = response.items.first {             if let tracks = try await playlist.with(.tracks, preferredSource: .catalog).tracks {                 if let allTracks = try await getAllTracks(tracks: tracks) {                     Logger.log(.success, "\(playlist.name) has \(allTracks.count) tracks")                     return allTracks                 } else {                     Logger.log(.fire, "Could not fetch any tracks for \(playlist.name)")                 }             } else {                 Logger.log(.fire, "With tracks on \(playlist.name) returns nil for tracks")             }         } else {             Logger.log(.warning, "Could not find playlist with id: \(id)!")         }     } catch {         Logger.log(.error, "Could not: \(error)")     }     return nil } It might be noticed that I've not attempted any concurrency, instead using a while loop to fetch all tracks, serially, in batches. The reason for this is because, I don't think it speeds things up at all, because I don't think we can't use .nextBatch() in parallel async calls. In the Apple Music API MusicDataRequest example in my initial post, I can get the total number of tracks in a playlist by looking at the meta.total value and then leverage async calls, using the right offsets to get "next batch" tracks in parallel calls. This significantly improves the speed on playlists with a large number of tracks. I'm not sure that a MusicItemCollection can provide us with something equivalent to meta.total, and it doesn't appear that .nextBatch() takes an offset argument. So I'm not sure how to speed things up better than what I've come up with here. Of course, it's highly probable that I am mistaken about that. That said, I can now get all tracks using MusicLibraryRequest to fetch a playlist and using .with on the resultant MusicItemCollection<Playlist>, followed by using .hasNextBatch and .nextBatch(), which is what I was looking for. Thanks again, @talkingsmall!
Post not yet marked as solved
6 Replies
0 Views
Hi @JoeKun & @david-apple, I have found that if I retrieve the items by using preferredSource: .catalog in the .with, per the below, that the MusicLibrary edit does not crash. excluding preferredSource or using preferredSource: .library in the .with will cause the crash. if let tracksToAdd = try await playlist.with(.tracks, preferredSource: .catalog).tracks {    // add tracks to target playlist } I've updated the ticket accordingly. Also, as a side note, it seems that .with only returns a max of 100 tracks. I'll keep testing and raise under separate cover, if I can't figure that out.
Post not yet marked as solved
6 Replies
0 Views
As a follow up, the following does work: for track in tracksToAdd { try await MusicLibrary.shared.add(track, to: targetPlaylist) } However, this is adding, not editing. The thing is that I’m really interested in the edit functionality, so as to be able to remove tracks.
Post not yet marked as solved
6 Replies
0 Views
Thanks @david-apple, I've raised a ticket (FB10328182) on Feedback Assistant as you've suggested.
Post not yet marked as solved
4 Replies
0 Views
I'm hopeful, that the **** may work, but I'm waiting on an iOS16 device to test on: @available(iOS 16.0, *) func getCuratorPlaylistsFromPlaylist2(playlistId: String) async throws -> String? {     do {         var request = MusicCatalogResourceRequest<MusicKit.Playlist>(matching: \.id, equalTo: MusicItemID(playlistId))         request.properties = [.curator] // adding curator to the request         let response = try await request.response()         Logger.log(.success, "Playlists Response: \(response.items)") // this is a collection of playlists, as expected.         Logger.log(.info, "Playlists Item Count: \(response.items.count)") // this is always 1, as expected         for item in response.items {             Logger.log(.info, "Item: \(item)") // shows the playlist's id, name, curatorName, and maybe more with .curator added             Logger.log(.info, "Type of Item: \(type(of: item))") // type is Playlist             if let curatorPlaylists = item.curator?.playlists {                 Logger.log(.info, "Curator Playlists: \(curatorPlaylists)") // hopeful here!             } else {                 Logger.log(.warning, "No Curator Playlists!")             }         }     } catch {         // handle error         Logger.log(.error, "Could not findCuratorOfPlaylist \(error)")     }     return nil }
Post not yet marked as solved
4 Replies
0 Views
Can't seem to do this either, using the curatorName that can be found in either of the two above attempts. @available(iOS 15.4, *) func searchForCurator(name: String) async throws -> MusicItemCollection<Curator>? {     do {         Logger.log(.error, "Doing search for curator by name...")         let request = MusicCatalogSearchRequest(term: name, types: [Curator.self])         let response = try await request.response()         Logger.log(.info, "Response curators: \(response.curators)") // MusicItemCollection<Curator>()         Logger.log(.info, "Response curators isEmpty: \(response.curators.isEmpty)") // true     } catch {         // handle error     Logger.log(.error, "Could not searchForCurator \(error)")     }     return nil }
Post not yet marked as solved
4 Replies
0 Views
Here's something similar to the above, but using .moreByCurator, though I'm not sure if I'm using it right, as I never get results. @available(iOS 15.4, *) func getCuratorPlaylistsFromPlaylist(playlistId: String) async throws -> String? {     do {         let request = MusicCatalogResourceRequest<MusicKit.Playlist>(matching: \.id, equalTo: MusicItemID(playlistId)) //catalogId         let response = try await request.response()         Logger.log(.success, "Playlists Response: \(response.items)") // this is a collection of playlists, as expected.         Logger.log(.info, "Playlists Item Count: \(response.items.count)") // this is always 1, as expected         for item in response.items {             Logger.log(.info, "Item: \(item)") // shows the playlist's id, name, and curatorName             Logger.log(.info, "Type of Item: \(type(of: item))") // type is Playlist             let morePlaylists = item.moreByCurator // not sure this is the right way to use .moreByCurator             Logger.log(.info, "More Playlists: \(String(describing: morePlaylists))") // This is always nil!         }     } catch {         // handle error         Logger.log(.error, "Could not findCuratorOfPlaylist \(error)")     }     return nil }
Post not yet marked as solved
4 Replies
0 Views
Trying some more things, admittedly not knowing what I'm doing: This still requires me to know the catalogId of a playlist that exists as a catalog resource, but it get's me the curatorId, which seems hopeful. My problem is searching for the curator using a MusicCatalogResourceRequest yields no results. func findCuratorOfPlaylist(playlistId: String) async throws -> String? {     do {         var requestURLComponents = URLComponents()         requestURLComponents.scheme = "https"         requestURLComponents.host = "api.music.apple.com"         requestURLComponents.path = "/v1/catalog/us/playlists/\(playlistId)/curator" // Got this idea from @snuff4         if let url = requestURLComponents.url {             let dataRequest = MusicDataRequest(urlRequest: URLRequest(url: url))             let dataResponse = try await dataRequest.response()             let decoder = JSONDecoder()             let response = try decoder.decode(AppleMusicGetCuratorResponse.self, from: dataResponse.data)             Logger.log(.info, "Curator Response: \(response)") // this returns the id, name, and kind (.editorial)             let items = response.data             let id = items[0].id             Logger.log(.info, "Searching for Curator with ID of \(id)")             let request = MusicCatalogResourceRequest<Curator>(matching: \.id, equalTo: id)             let resp = try await request.response()             Logger.log(.info, "Find Curator Request Response Items: \(resp.items)") // MusicCatalogResourceResponse<Curator>()             Logger.log(.info, "Is Curator Request Response empty: \(resp.items.isEmpty)") // true         }     } catch {         // handle error         Logger.log(.error, "Could not findCuratorOfPlaylist \(error)")     }     return nil }
Post marked as solved
7 Replies
0 Views
Thanks for the pro follow-up, @JoeKun. I've been dabbling, and it looks like add(to:) is singular, while edit would allow me to achieve my use case. Nice to see these new features coming to MusicKit!
Post not yet marked as solved
6 Replies
0 Views
FYI: @dutton @JoeKun, After testing, the problem still exists, and I've updated FB9857866 accordingly.
Post not yet marked as solved
2 Replies
0 Views
UPDATE! It appears that this is now possible. See here!
Post marked as solved
4 Replies
0 Views
@david-apple This is great! I'm so happy that this feature is now available... it's been a long time coming. It appears that I need to target iOS16, which means I gotta update my test device, which is an iPod touch generation 7. I look forward to getting into this soon.