Thanks DTS Engineer!
Sadly, you folks have gone anonymous. No more crying to @JoeKun when I need him 😄
I have filed the bug report (FB15454154), but to be frank, I've filed some that have gone without a response for years. Others, I've noticed have actually been fixed quietly, and yet the Radar remains open.
Just sayin'...
Important to note is that this happens on your website, so "how I'm calling Music Kit" isn't applicable. There's something sketchy with the iframe implementation.
Post
Replies
Boosts
Views
Activity
Hi @CMExpertise,
Unfortunately not. I'm basically a user, same as you.
While I could be doing something wrong, my best current guess is that this is a transient issue that appears every so often that somehow gets magically resolved.
@JoeKun or @david-apple don't seem to be replying so perhaps they've moved to other things. Not sure how to get more attention on this issue.
@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.
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.
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.
@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.
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.
@JoeKun I spelled your name wrong 😊 @david-apple any help here?
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.
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.
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.
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!
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.
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.
Thanks @david-apple,
I've raised a ticket (FB10328182) on Feedback Assistant as you've suggested.