Post

Replies

Boosts

Views

Activity

Reply to SwiftUI NavigationLink freezing when tapped
I recently finished implementing programmatic SwiftUI navigation in my app and I ran into this. In my experience, it was related to using a different custom type for the navigationDestinations on different screens and having their root NavigationStack path bound to a NavigationPath. To fix it, I ended up making my own NavigationDestination enum with a list of all possible navigation destinations in my app. Now, every .navigationDestionation in my app is for NavigationDestination.self and the root NavigationStack is bound to an array of [NavigationDestination]. No more freezing!
Jan ’23
Reply to Request: An API to remove from Library/Playlist.
I would also love the ability to delete items from a user's library. In case any Apple folks are looking for feedbacks regarding this, shortly after WWDC I filed a feedback (FB10042390) to add that functionality to the new MusicLibrary class and last year I filed one to add the functionality to the Apple Music API as FB9195700. Fingers crossed this gets added!
Jul ’22
Reply to Thoughts on MusicLibraryRequest as a replacement for MPMediaQuery
@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!
Jul ’22
Reply to Exposing library-related metadata on library Songs
@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!
Jul ’22
Reply to Thoughts on MusicLibraryRequest as a replacement for MPMediaQuery
@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.
Jul ’22
Reply to MusicLibraryRequest to get all tracks from a playlist (iOS 16 beta)
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)                   })         }     }
Jun ’22
Reply to Thoughts on MusicLibraryRequest as a replacement for MPMediaQuery
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
Jun ’22
Reply to Is the MusicItemID different per locale?
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.
Mar ’22