I still have not figured out what is causing this. Am I really the only person getting cpu_usage_fatal crashes caused by CoreData/CloudKit syncs?
Post
Replies
Boosts
Views
Activity
I am seeing very similar behavior on iOS 15 in my app. Installing it fresh on iOS 14 allows it to upload the initial data and start syncing, but doing the same on the latest iOS 15 beta causes the app to crash while in the background using 100% CPU, and it never manages to upload any data.
I've submitted this as FB9489988, which also references an issue I've seen on both iOS 14 and iOS 15 around cpu_usage_fatal crashes when syncing with iCloud.
FB9558364
I filed feedback FB9851840 (including sample project) reporting this very issue just moments before seeing this post. I get the same error as you. The documentation page for MusicPlayer does not mention it is available in macOS, but says it is in Mac Catalyst 15.0+, so it's not clear what is supposed to work. Perhaps @JoeKun can shed some light on the expected behavior of this API? I am really hoping to bring my iOS music player to the Mac in the future.
Well isn't this interesting! With absolutely no disrespect meant to the MusicKit team, who are no doubt limited by time, resources, and internal corporate priorities us outsiders have no idea about, It is frustrating to play around with this token and see so many of the features I would love to include in my app, such as:
Deleting library items
Time-synced lyrics
Artist images
Available audio qualities
The MusicKit API and web API have already come a long way towards leveling the playing field for third party Apple Music apps, but there are still lots of gaps, which can make our apps feel like second-class citizens. Maybe there are licensing issues preventing features like lyrics, but it is impossible for developers to know the surrounding context, and we are just stuck filing feedbacks and awaiting WWDC each year.
If the permissions included in this token were officially supported, my app would be better for it. Here's hoping!
I haven't played around at all with the curator stuff -- @JoeKun, that post is a great overview and has me playing around in Postman.
One thing I haven't been able to figure out: is there any particular rhyme or reason to the order in which the API returns the Playlist relationships? Taking your example of Apple Music Pop, I had expected the API to return the playlists in an order roughly equivalent to the page in Music.app. The most popular playlists first, which as I look now at Music.app on my Mac are "Today's Hits" and "Viral Hits."
These are the two playlists returned from this endpoint, which I don't see anywhere on the Music.app Apple Music Pop page:
"name": "Tom Mann: Songbook",
"url": "https://music.apple.com/us/playlist/tom-mann-songbook/pl.b5a5fecc589d4056be182e8cafdc0fc0"
and
"url": "https://music.apple.com/us/playlist/ivan-dorn-essentials/pl.362b70aa54e54989a08a2e2df4ff48d3",
"name": "Ivan Dorn Essentials",
The Albums relationship for Artist seems to be more or less by popularity, but that doesn't seem to be the case here. Any hints at how to best make use of that resource?
It turns out that ids are different across different catalog locales, but there is an easy way in the API to find equivalent items. That's actually the first topic covered in this WWDC session from last year: https://developer.apple.com/wwdc21/10293.
If I were you, I'd be cautious about caching that id information, or at least making sure you gracefully handle the case where an item is no longer accessible at the id you have saved for it. I've seen a handful of cases in my own app where an album's id changes for whatever reason.
Feel free to dupe the new FB I submitted asking for this functionality to be added to the new MusicLibrary class: FB10042390
Test commenting
Thanks, @JoeKun! My bad -- yes, I suppose just because they aren't the key paths I want doesn't mean they aren't key paths to begin with ;)
I guess the proper request is something like: please add the following properties to the following protocols to enable more specific filtering in MusicLibraryRequests:
LibrarySongFilter
albumTitle
artistName
composerName
title
LibraryAlbumFilter
artistName
title
LibraryArtistFilter
name
Same question here. I'm hoping this will become clearer in future beta seeds.
I've been trying to get better at structured concurrency, so I took a stab at a function that collects all of the tracks from a library playlist. I think the key here is using the hasNextBatch property on MusicItemCollection. I'm far from on expert in this world, so I'm sure this code can be improved. In the meantime, here's what I came up with. It correctly returns the number of songs on playlists with more than 100 songs.
func getTracksFromPlaylist(name: String) async throws {
var request = MusicLibraryRequest<Playlist>.init()
request.filter(text: name)
let result = try! await request.response()
if let first = result.items.first {
let withTracks = try await first.with(.tracks)
guard let startingTracks = withTracks.tracks else {
return
}
try await getAllTracksFromPlaylist(startingTracks: startingTracks, group: nil)
}
}
func getAllTracksFromPlaylist(startingTracks: MusicItemCollection<Track>, group: ThrowingTaskGroup<[Track],Error>?) async throws {
//If the group didn't come in as nil, that means startingTracks has a next batch that needs to be fetched and processed
if var group = group {
if let currentSetToProcess = try await startingTracks.nextBatch() {
//Add the tracks from this batch to the group
group.addTask {
return Array(currentSetToProcess)
}
//If this set has a next batch, run the function again. Once currentSetToProcess.hasNextBatch returns false, the group will complete since it is not awaiting anything else.
if currentSetToProcess.hasNextBatch {
try await getAllTracksFromPlaylist(startingTracks: currentSetToProcess, group: group)
}
}
//If the group came in as nil, create the group and work through the first batch of tracks.
} else {
try await withThrowingTaskGroup(of: [Track].self, body: { group in
//The array to store the final set of tracks
var allTracks: [Track] = []
//Add the starting tracks to the group
group.addTask {
return Array(startingTracks)
}
//If there are more than 100 tracks on the playlist, this is true and means we need to loop through and get the other tracks.
if startingTracks.hasNextBatch {
try await getAllTracksFromPlaylist(startingTracks: startingTracks, group: group)
}
//As arrays of tracks are added to the group, append them to our allTracks array.
for try await tracks in group {
allTracks.append(contentsOf: tracks)
}
//Finally, when the group has returned all of the tracks, print the final amount.
print(allTracks.count)
})
}
}
@JoeKun this is amazing! I'm so grateful for all of these improvements. Having the ability to return MusicKit Album objects from an MPMediaItemPropertyAlbumPersistentID makes it so much simpler for me to adopt all this awesome new stuff. I have noticed a couple of issues, for which I've filed feedbacks:
FB10581774 - Non-alphanumeric/whitespace characters don't work in equalTo matching
When testing the new LibraryAlbumFilter properties, I think I've found an issue where matching on \.artistName or \.title fails to return results when using the equalTo match type and matching on a string containing non-alphanumeric or whitespace characters. The below code fails to return any results when using equalTo, but using the contains version of the filter function correctly returns all the R.E.M. albums in my library. I have also reproduced this same issue when matching on \.title when the album name contains parentheses.
var nameRequest = MusicLibraryRequest<Album>.init()
nameRequest.filter(matching: \.artistName, equalTo: "R.E.M.")
do {
let nameResponse = try await nameRequest.response()
print(nameResponse.items)
} catch {
print("name request error: \(error)")
}
FB10581868 - Some MPMediaItemPropertyAlbumPersistentID matches fail
Using the below code successfully returns MusicKit albums in most cases, but sometimes it fails to. Here's my code for the match starting with an MPMediaItem called rep.
let idString = String(rep.albumPersistentID)
var request = MusicLibraryRequest<Album>()
request.filter(matching: \.id, equalTo: MusicItemID(idString))
do {
let response = try await request.response()
print(response.items)
} catch {
print("id request error: \(error)")
}
In cases where matching on this ID doesn’t work, I then do a backup search by .artistName and .title, which returns the album and shows a different ID.
The ones that fail show an ID with a huge negative number. It seems like the original MPMediaItemPropertyAlbumPersistentID is unsigned, but the ID MusicKit is using is? Something like that?
Here's what prints when I print the ID:
Looking for Defeater - Abandoned (Deluxe Edition), id: 9601744824086363441
Here's what prints when I show the results of the .artistName and .title match (using contains, because as described above, using equalTo will fail because of the parens in the album title :D)
MusicItemCollection<Album>(
items: [
Album(id: "-8844999249623188175", title: "Abandoned (Deluxe Edition)", artistName: "Defeater")
]
)
Should I be converting the MPMediaItemPropertyAlbumPersistentID to a string like this, or is there another to instantiate it as a MusicItemID?
FB10582021 Expose catalogID if present for Library albums
Being able to use an album’s MPMediaItemPropertyAlbumPersistentID to fetch the library version is great, but unless I’m missing something, you then lose access to the album's Apple Music catalog ID. It would be great to be able to still access the catalog ID as well as the local ID.
Running dump() on the fetched object, it looks like the catalog ID is embedded in some of the properties, such as the Play Parameters.
▿ playParameters: Optional(MusicKit.PlayParameters(id: -8844999249623188175, kind: "album", isLibrary: Optional(true), catalogID: Optional(MusicKit.MusicCatalogID(value: 1485062012, kind: MusicKit.MusicCatalogID.Kind.adamID)), deviceLocalID: Optional(MusicKit.MusicDeviceLocalID(value: -8844999249623188175, databaseID: 9C47D30D-37C1-4F5C-B5AF-010C9A1052F1)), rawValues: [:]))
As always, please let me know if I can provide any other information here or in the Feedbacks.
@JoeKun
This is fantastic news, and in my early testing these properties are working great. I have two quick questions for whenever you get a chance.
The description of lastPlayedDate uses some specific phrasing that caught my eye: "The date when the user last played the song on this device." Does it really mean it will only show the date the item was played on that device, or will it report the last play time across any device linked to the user's iCloud Music Library, the way MPMediaItem's lastPlayedDate works?
How is lastPlayedDate determined for the Album object? There are a handful of albums in my test library where none of the songs have play counts, last play times, or last skip times, but the MusicKit album itself has a value for this property.
Thanks!
@JoeKun
Got it -- that makes sense. Indeed, thanks to the excellent API design, many of the use-cases I can think of are covered by other functions, such as being able to use with(_:preferredSource:) to pull in Library and Catalog information in one fell swoop.
The one thing I can't find a solution for is being able to get a link to share the catalog version of the library album or song resource. The URL property seems to always return nil. Perhaps that could still be populated with the link to the resource using the catalog ID (if available for a given resource)?
Is your expectation that the /me/library/albums/[id]?relate=catalog API endpoint should still work using this new ID approach? I'm able to return a result for a library song using the below MusicDataRequest, but I notice that the id for a library song is still the the old school i.XXXXXXXX format:
MusicDataRequest(urlRequest: URLRequest(url: URL(string: "https://api.music.apple.com/v1/me/library/songs/i.XMDXXbxtOl0kZ84?relate=catalog")!))
Now that the underlying MPMediaItemPropertyAlbumPersistentID is being used as the local ID for albums, the below MusicDataRequest returns a "Resource with Requested id was not found" error.
MusicDataRequest(urlRequest: URLRequest(url: URL(string: "https://api.music.apple.com/v1/me/library/albums/-8844999249623188175?relate=catalog")!))
One way or another, it would be very helpful to have a reliable way of translating between catalog and local IDs.
On another note, I've made a little more progress on investigating the issue of some MPMediaItemPropertyAlbumPersistentIDs not returning an album. If I modify my code to convert the unsignedUInt64 returned from the albumPersistentID to a signed Int64 as follows, I'm able to successfully return an album for any ID I give it. It's easy enough to do, but probably confusing for folks not expecting to have to do that.
let idString = String(Int64(bitPattern: rep.albumPersistentID))
Thanks!