Posts

Post marked as solved
3 Replies
@timothy.hatcher That worked like a charm! I Googled like a madman looking for how to do this, and your answer made my day. Thank you for taking the time to help me out.
Post not yet marked as solved
10 Replies
Hi @kokomola, What I've written prior still applies. I will add that sometimes, for whatever reason this stops stops working and then starts working again after an undetermined amount of time. I have no explanation for why it sometimes doesn't work.
Post not yet marked as solved
6 Replies
I saw it recently again too. My best guess is that some transient issue that gets quietly fixed or magically resolves itself. The real bug-a-boo here is that errors are not thrown, so I cannot catch and deal with them to provide users with any type of error message, which equates to a bad user experience.
Post not yet marked as solved
10 Replies
@JoeKun This bug is back again. Long story, short: I redeployed an app that hasn't changed code-wise. This line causes stops the function that it's in and throws no error. It also does not edit the playlist. try await MusicLibrary.shared.edit(targetPlaylist, items: items) The only things that have changed, I imagine, are related to new releases of software since WWDC 2023.
Post not yet marked as solved
6 Replies
This started working again, which is mysterious esp. considering I changed no code. If anyone else has experienced this, please let me know that I'm not alone here.
Post not yet marked as solved
6 Replies
@JoeKun I spelled your name wrong 😊 @david-apple any help here?
Post not yet marked as solved
10 Replies
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
8 Replies
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
8 Replies
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
8 Replies
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
10 Replies
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
10 Replies
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
10 Replies
Thanks @david-apple, I've raised a ticket (FB10328182) on Feedback Assistant as you've suggested.
Post not yet marked as solved
4 Replies
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
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 }