MusicKit

RSS for tag

Let users play Apple Music and their local music library from your app using MusicKit.

Posts under MusicKit tag

134 Posts
Sort by:

Post

Replies

Boosts

Views

Activity

MusicKit SDK for Android update?
Hi team, Are there any plans to update the Apple Music SDK for Android? With the upcoming release of the new music experience on iOS and the Apple Music app for Android getting a whole slew of features over the past month, is the (rather out of date - last update was December 2021) MusicKit SDK for Android going to get the new release that would enable developer avail of the new feature or is that SDK abandoned at this stage?
1
0
519
Oct ’23
Terrible performance when using MusicKit's Artwork with UIKit
I'm building a UIKit app that reads user's Apple Music library and displays it. In MusicKit there is the Artwork structure which I need to use to display artwork images in the app. Since I'm not using SwiftUI I cannot use the ArtworkImage view that is recommended way of displaying those images but the Artwork structure has a method that returns url for the image which can be used to read the image. The way I have it setup is really simple: extension MusicKit.Song { func imageURL(for cgSize: CGSize) -> URL? { return artwork?.url( width: Int(cgSize.width), height: Int(cgSize.height) ) } func localImage(for cgSize: CGSize) -> UIImage? { guard let url = imageURL(for: cgSize), url.scheme == "musicKit", let data = try? Data(contentsOf: url) else { return nil } return .init(data: data) } } Now, everytime I access .artwork property (so a lot of times) the main thread gets blocked and the console output gets bombared with messages like these: 2023-07-26 11:49:47.317195+0200 Plum[998:297199] [Artwork] Failed to create color analysis for artwork: <MPMediaLibraryArtwork: 0x289591590> with error; Error Domain=NSCocoaErrorDomain Code=4099 "The connection to service named com.apple.mediaartworkd.xpc was invalidated: failed at lookup with error 159 - Sandbox restriction." UserInfo={NSDebugDescription=The connection to service named com.apple.mediaartworkd.xpc was invalidated: failed at lookup with error 159 - Sandbox restriction.} 2023-07-26 11:49:47.317262+0200 Plum[998:297199] [Artwork] Failed to create color analysis for artwork: file:///var/mobile/Media/iTunes_Control/iTunes/Artwork/Originals/4b/48d7b8d349d2de858413ae4561b6ba1b294dc7 2023-07-26 11:49:47.323099+0200 Plum[998:297013] [Plum] IIOImageWriteSession:121: cannot create: '/var/mobile/Media/iTunes_Control/iTunes/Artwork/Caches/320x320/4b/48d7b8d349d2de858413ae4561b6ba1b294dc7.sb-f9c7943d-6ciLNp'error = 1 (Operation not permitted) My guess is that the most performance-heavy task here is performing the color analysis for each artwork but IMO the property backgroundColor should not be a stored property if that's the case. I am not planning to use it anywhere and if so it should be a computed async property so it doesn't block the caller. I know I can move the call to a background thread and that fixes the issue of blocking main thread but still the loading times for each artwork are terribly slow and that impacts the UX. SwiftUI's ArtworkImage loads the artworks much quicker and without the errors so there must be a better way to do it.
4
0
935
Feb ’24
MPMediaQuery not returning updated results
I have noticed changes Apple Music made to my library, take in particular a changed album edition that is reflected in how the title is listed. I can see the new title in the Music app in two different devices. On one device MPMediaQuery returns the album with the new title. The other device (an iPad with less memory, in case that matters) is still returning the old edition. Is there anything I can do to make sure the data returned is up to date and matches what is seen in the Music app?
0
0
644
Jul ’23
App hangs while opening from background
Hi, My app hangs for about a second when I open the app when it's been in background phase. It doesn't hang every time, but it hangs randomly (like once in 10 times) when I open the app again. I am not able to reproduce what causes this hang. Hang Detection provides hang logs (spindump) when hang occurs. It has something to do with MusicKit as logs mention it but I cannot understand what it is. Please help me understand these logs. I am running iOS 17 beta 1 with MusicKit and ApplicationMusicPlayer. Heaviest stack for the main thread of the target process: 69 start + 2104 (dyld + 87288) [0x1b719b4f8] 69 ??? (Timed + 32864) [0x104ae8060] 69 ??? (SwiftUI + 980040) [0x19840a448] 69 ??? (SwiftUI + 1071088) [0x1984207f0] 69 ??? (SwiftUI + 1667804) [0x1984b22dc] 69 UIApplicationMain + 340 (UIKitCore + 3740336) [0x196c602b0] 69 -[UIApplication _run] + 888 (UIKitCore + 3741260) [0x196c6064c] 69 GSEventRunModal + 164 (GraphicsServices + 4644) [0x1d6199224] 69 CFRunLoopRunSpecific + 600 (CoreFoundation + 527792) [0x1947fedb0] 68 __CFRunLoopRun + 1996 (CoreFoundation + 509348) [0x1947fa5a4] 68 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 16 (CoreFoundation + 626340) [0x194816ea4] 68 _dispatch_main_queue_callback_4CF + 44 (libdispatch.dylib + 75296) [0x19c404620] 67 _dispatch_main_queue_drain + 744 (libdispatch.dylib + 76056) [0x19c404918] 67 swift_job_runImpl(swift::Job*, swift::ExecutorRef) + 72 (libswift_Concurrency.dylib + 274348) [0x19f58cfac] 67 swift::runJobInEstablishedExecutorContext(swift::Job*) + 416 (libswift_Concurrency.dylib + 269688) [0x19f58bd78] 66 ??? (MusicKit + 4857464) [0x208aabe78] 66 ??? (MusicKit + 4856108) [0x208aab92c] 66 -[MusicKit_SoftLinking_MPMusicPlayerController nowPlayingItem] + 24 (MusicKit + 157364) [0x2086306b4] 66 -[MPMusicPlayerController nowPlayingItem] + 24 (MediaPlayer + 1358540) [0x1a681bacc] 66 -[MPMusicPlayerController _nowPlaying] + 372 (MediaPlayer + 1329552) [0x1a6814990] 66 -[MPMusicPlayerController onServer:] + 52 (MediaPlayer + 1333428) [0x1a68158b4] 63 -[MPMusicPlayerApplicationController _establishConnectionIfNeeded] + 1768 (MediaPlayer + 1553848) [0x1a684b5b8] 63 _NSXPCDistantObjectSimpleMessageSend1 + 60 (Foundation + 208348) [0x1937daddc] 63 -[NSXPCConnection _sendSelector:withProxy:arg1:] + 116 (Foundation + 208548) [0x1937daea4] 62 -[NSXPCConnection _sendInvocation:orArguments:count:methodSignature:selector:withProxy:] + 2160 (Foundation + 214664) [0x1937dc688] 62 __NSXPCCONNECTION_IS_WAITING_FOR_A_SYNCHRONOUS_REPLY__ + 16 (Foundation + 652944) [0x193847690] 62 xpc_connection_send_message_with_reply_sync + 264 (libxpc.dylib + 67488) [0x1fc00d7a0] 62 dispatch_mach_send_with_result_and_wait_for_reply + 60 (libdispatch.dylib + 127760) [0x19c411310] 62 _dispatch_mach_send_and_wait_for_reply + 540 (libdispatch.dylib + 126832) [0x19c410f70] 62 mach_msg + 24 (libsystem_kernel.dylib + 4692) [0x1da082254] 62 mach_msg_overwrite + 436 (libsystem_kernel.dylib + 83544) [0x1da095658] 62 mach_msg2_trap + 8 (libsystem_kernel.dylib + 3332) [0x1da081d04] *62 ??? (<31E57057-A9A0-3BE5-90CB-5C08E9683B34> + 217132) [0xfffffff007e0102c] Thank you. CC @JoeKun
2
0
1.1k
Oct ’23
Cannot load underlying module for 'MediaPlayer'
I'm getting the following error in Xcode and I can't figure out how to fix it. Cannot load underlying module for 'MediaPlayer' I've searched Google and have come across lots of other people with similar issues whereby they're importing something and it's producing this error alongside it. https://stackoverflow.com/questions/76256875/cannot-load-underlying-module-for-scenekit https://developer.apple.com/forums/thread/115059 https://stackoverflow.com/questions/32673866/cocoapods-cannot-load-underlying-module-for-x The error is being shown inline with this bit of code: import SwiftUI import MusicKit import MediaPlayer <-- this line here I can build and run the project without issue and it disappears. But will quickly reappear again a short while later and it's very annoying. How can I resolve this please?
1
1
1.5k
Jul ’23
Issues with MusicLibraryRequest on macOS/Mac Catalyst
I've been continuing to port my app to the Mac using Catalyst, and have discovered a couple of big issues with MusicLibraryRequest which make it not particularly usable. I've submitted FBs already, but figured a forum post wouldn't hurt :) filter(matching:) functions unavailable on macOS/Catalyst - FB12301718 The first thing I noticed when building for a macOS or Catalyst target is that a couple of functions are simply not available: I'm hoping this is a bug, as these methods are crucial to being able to interact with the library. request.filter(matching: .id, equalTo:) always returns empty on Mac Catalyst - FB12301908 I have not been able to successfully retrieve an Album using a MusicLibraryRequest. The result is always empty. In the feedback, I attached a sample project that makes an unfiltered MusicLibraryRequest, then takes the first album and tries to make a new MusicLibraryRequest filtered to that album's ID. Fetching albums by ID using this method works just fine on iOS 17. Thanks again for all your work on bringing these capabilities to the Mac!
2
0
1.2k
Jul ’23
MPMusicPlayerController on watchOS
Hi, I’m looking at using MusicKit in my watchOS app however I don’t seem to have any method of being able to play the audio though the recommended use of MPMusicPlayerController since it isn’t available on watchOS. This method works fine for iOS and iPadOS but not watchOS which seems bizarre considering we have full access to MusicKit but no way to actually play any audio. I’m trying to build an app that includes Apple Music through MusicKit but don’t have any way to actually play the audio. Is there a technical reason for this and if so is there any other way to play audio from MusicKit on watchOS. The docs for MPMusicPlayerController can be found here: https://developer.apple.com/documentation/mediaplayer/mpmusicplayercontroller
1
1
754
Oct ’23
MusicKit MusicCatalogResourceRequest for artists with top-songs returns 504 in non-json format
I'm making a request to get 10 artists with their top songs at once, but for some artists it will always fail with a 504. The response is also in HTML which leads to a decoding error. This is my code var request = MusicCatalogResourceRequest<Artist>(matching: \.id, memberOf: ids) request.properties = properties let response = try await request.response() where ids is MusicItemId. Below I have an input which will always fail 100% of the time, even when retried. 10 elements  - 0 : "51639"  - 1 : "331584"  - 2 : "120199"  - 3 : "45058"  - 4 : "284786497"  - 5 : "44984"  - 6 : "37299"  - 7 : "518462"  - 8 : "39525"  - 9 : "73568" Example response: [DataRequesting] Failed to parse body of response with status code Unknown (504):  <!DOCTYPE html> <html lang="en"> <head>   <style>     body {       font-family: "Helvetica Neue", "HelveticaNeue", Helvetica, Arial, sans-serif;       font-size: 15px;       font-weight: 200;       line-height: 20px;       color: #4c4c4c;       text-align: center;     }     .section {       margin-top: 50px;     }   </style> </head> <body> <div class="section">   <h1>&#63743;</h1>   <h3>Gateway Timeout</h3>   <p>Correlation Key: WFRI6Q5HXAUJYXGNRKQ6YTBYIM</p> </div> </body> </html> I have also tried batching these into 2 requests of 5 artists instead of 1 request of 10 artists which still fails. However, I do have sets of 10 artists that work fine. Anyone know why?
3
1
2.1k
May ’24
Apple Music Kit Web API - User's Top Played Songs/Artists
Hello everyone! I am using the web version of the Apple Music Kit API, and similar to how Apple is able to produce a user's year in rewind playlist showing the most played artists/songs from a year, I am trying to replicate it for either a year or all time. I have been searching the internet for days trying to figure it out, but I've been completely stuck. I am able to make the following HTTP request successfully. GET https://api.music.apple.com/v1/me/library/songs/ which returns back the user's library of songs, but I haven't figured out how to get the play count. I have tried adding a query like such ?extend=playCount, but that doesn't work. I can see here that the Swift Music Kit API is able to extend a play count property, but I haven't been able to figure it out for Web. Ideally, I am looking for an endpoint that just shows a user's top artist/tracks similar to Spotify, however, whenever I try to use the heavy in rotation endpoint here, it always returns an empty array. The way that I have described is the long-roundabout way where I'll have to fetch each individual song and sort by playCount. But if anyone happens to know how I can do either of the options I've described, it will be truly appreciated ! I've seen other forums posts from years ago, but hopefully there's been a discovered way. Thank you!
2
1
1.8k
Sep ’23
iOS 16 Music Kit MusicLibrary methods have stopped working
Hi there, I have a related forum thread here and a Feedback Assistant ticket open, but this issue seems different. Sometime within the last 2-3 weeks, code related to MusicLibrary has stopped working. None of my code has changed. For example, the below two snippets used to work fine:  for track in newTracks {    try await MusicLibrary.shared.add(track, to: targetPlaylist)  } try await MusicLibrary.shared.edit(targetPlaylist, items: items) newTracks and items are both fetched using: try await targetPlaylist.with(.tracks, preferredSource: .catalog).tracks Using preferredSource: .catalog was a workaround used to address the issue in the aforementioned post above. All iOS 16 capable functions are decorated with: @available(iOS 16, *) or in an if block: if #available(iOS 16, *) {... What's happening is that the following is showing up in the console: 2022-11-28 23:31:11.279648+0700 MyApp[38653:6736450] [core] Attempted to register account monitor for types client is not authorized to access: {(     "com.apple.account.iTunesStore" )} 2022-11-28 23:31:11.279718+0700 MyApp[38653:6736450] [Default] <ICUserIdentityStoreACAccountBackend: 0x282adb520> Failed to register for account monitoring. err=Error Domain=com.apple.accounts Code=7 "(null)" 2022-11-28 23:31:11.279758+0700 MyApp[38653:6736450] [Default] ICUserIdentity - Unable to retrieve DSID for userIdentity=<ICUserIdentity 0x2806eb120: [Active Account: <unresolved>]> - error=Error Domain=com.apple.accounts Code=7 "(null)" These errors are not caught by a do/catch block, but I assume they are related to the issue, and I believe they have to do with MyApp trying to access things that Music Kit thinks it's not supposed to. For example, if MyApp attempts to work with a playlist that it did not create, errors would be expected, thrown errors. The thing is that I know I'm working with resources that are created by MyApp. In fact, in trying to test this, I just tried to create a playlist with the below, and the same behavior is occurring: @available(iOS 16, *) func createPlaylist2(name: String, description: String) async -> MusicKit.Playlist? {     do {         Logger.log(.info, "Creating Playlist: \(name)")         Logger.log(.shrug, "Does this work?")         let newPlaylist = try await MusicLibrary.shared.createPlaylist(name: name, description: description) // <= Things stop here!         Logger.log(.success, "New playlist created: \(newPlaylist)") // <= this isn't logged.         return newPlaylist // <= nothing is returned     } catch {         Logger.log(.error, "Could not create new playlist: \(error)") // <= no error logged.     }     return nil } The result is: 2022-11-29 00:15:01.875064+0700 MyApp[38794:6760471] [core] Attempted to register account monitor for types client is not authorized to access: {(     "com.apple.account.iTunesStore" )} 2022-11-29 00:15:01.875372+0700 MyApp[38794:6760471] [Default] <ICUserIdentityStoreACAccountBackend: 0x283005720> Failed to register for account monitoring. err=Error Domain=com.apple.accounts Code=7 "(null)" 2022-11-29 00:15:01.876677+0700 MyApp[38794:6760323] [EntityQuery] Finished executing query in 0.000999928s 2022-11-29 00:15:01.889055+0700 MyApp[38794:6760323] [EntityQuery] Finished fetching results in 0.0120001s 2022-11-29 00:15:01.891235+0700 MyApp[38794:6760329] [core] Attempted to register account monitor for types client is not authorized to access: {(     "com.apple.account.iTunesStore" )} 2022-11-29 00:15:01.891684+0700 MyApp[38794:6760329] [Default] <ICUserIdentityStoreACAccountBackend: 0x283005720> Failed to register for account monitoring. err=Error Domain=com.apple.accounts Code=7 "(null)" 📘 Creating Playlist: TEST PLAYLIST 🤷🏻‍♀️ Does this work? 2022-11-29 00:15:06.697374+0700 MyApp[38794:6760329] [] nw_path_necp_check_for_updates Failed to copy updated result (22) What's really nasty is that errors are not thrown, so they can't be caught and handled in a catch block. I know that iOS 16.1 got released around the end of October, but I really don't know what's going on here. The behavior is showing up in both prod and when testing locally. Any help would be most appreciated. @JoeKhun: Did I miss the memo?
9
1
3.9k
May ’24
Detect the end of queue in MPMusicPlayerController
Hello, this is building off of another post in which several other posters and I had already attempted solving the issue in hacky ways. I am using MPMusicPlayerController.applicationQueuePlayer. My end goal here is to dynamically add items to the queue when it has ended based on my application's business logic. There is no way for me to know what these items will be when I am initially setting the queue. I have an updated implementation that seems to cover most edge cases, except for a glaringly obvious one – if there is just one item in the queue, and the user skips the track via MPRemoteCommandCenter (eg. lock screen), then it does not work. Currently, when I receive a MPMusicPlayerControllerPlaybackStateDidChange notification, I run this block: if player.playbackState == .paused,            player.currentPlaybackTime == 0,            player.indexOfNowPlayingItem == 0 {             EndOfQueueManager.handle()         } In the absence of a mechanism to detect the end of the queue from the framework, I would love to add the ability to add a target to MPRemoteCommand, like you can do for AVPlayer. I have tried to do exactly that, but it does not work: MPRemoteCommandCenter.shared().nextTrackCommand.addTarget { (event) -> MPRemoteCommandHandlerStatus in         if queue.count == 1 {             EndOfQueueManager.handle() }         return .success } I already have a functioning AVPlayer implementation that achieves my goal without any compromises or edge cases. I would be very disappointed if there is no way to do this with MPMusicPlayerController – being notified about the queue ending feels like a fairly rudimentary API hook.
1
2
748
4w
Can't access chart playlists for a specific genre
I am trying to offer my users a wide variety of playlists, like Apple Music does in the "explore" section. I fetched the charting playlists for the current storefront, but that's as far as I get. For example, I fetch the top charts genres first. If a user selects a genre I want to display the playlists for this genre, so I call the charts endpoint with the genre as the id but I can't get a response. path = "/v1/catalog/\(storefrontID)/charts" components.queryItems = [ URLQueryItem(name: "types", value: "playlists"), URLQueryItem(name: "chart", value: "most-played") ] if let id = id { let genreQuery = URLQueryItem(name: "genre", value: id) components.queryItems?.append(genreQuery) } Even weirder, I get exactly one genre "Musik" (which isn't a genre) with identifier "34" and storefrontId "de" where it works and I get my playlists. All other genre return empty responses. I try to use AppleMusic API with MusicKit as an addition, but there doesn't seem to be a solution for this problem either.
2
1
960
Aug ’23
Issue setting a queue with library and non-library items at the same time (plus a couple more MusicKit issues)
As the summer continues, I have been diving deeper and deeper into MusicKit, largely with great results. A few issues have arisen that I've outlined here, feedbacks already filed and numbers included here. All of this happens on the lasted developer beta and latest Xcode beta. Thanks! FB10967343 - Setting the queue with library and non-library items at the same time doesn't work correctly In my app, I am working on a feature that lets a user shuffle songs from a collection of albums that may or may not be in their library. However, I’ve discovered an issue where the queue does not seem to work correctly when mixing these types. I’ve attempted to load ApplicationMusicPlayer by creating a Queue and to load applicationQueuePlayer using a MPMusicPlayerPlayParametersQueueDescriptor, but the same issue occurs each time. The queue is able to play songs from the same source, but if it’s been playing a library song and tries to move to a non-library song, the queue stops.  The first thing I do is pick random songs from each album, using a MusicLibraryRequest or a MusicCatalogResourceRequest as appropriate, then taking a randomElement() from the ensuing MusicItemCollection for the album.  I append each track to an array, which I then cast to MusicItemCollection so I’ve now got a MusicItemCollection consisting of the tracks I want. If I’m in MusicKit land, I simply set the queue as follows:  player.queue = ApplicationMusicPlayer.Queue(for: tracks) It takes a bit more doing in MediaPlayer, but in theory this should also work, right?    do {         let paramObjects = tracks.compactMap {             $0.playParameters         }         let params = try paramObjects.map({try JSONEncoder().encode($0)}) let dicts = try params.compactMap {               try JSONSerialization.jsonObject(with: $0, options: []) as? [String:Any]           }           let finalParams = dicts.compactMap {                 MPMusicPlayerPlayParameters(dictionary: $0)             } let descriptor = MPMusicPlayerPlayParametersQueueDescriptor(playParametersQueue: finalParams) mediaPlayer.setQueue(with: descriptor) } catch { print(error) } In either case, the following issue occurs: say that I end up with a queue made up of one library song, then one non-library song. The player will play just the first song, then it acts as if the queue has ended. Say that it has two non-library songs, then one library song. Just the two non-library songs play. Indeed, printing queue.entries shows just the number of items that were from the same source type. FB10967076 - Publishing changes from background thread error when inserting queue items When using the .insert method on ApplicationMusicPlayer.Queue on the last iOS 16 and Xcode betas, it returns a “Publishing changes from background thread” error even though the function I’m doing in is marked as a @MainActor and the stacktace indicates it was on the main thread. FB10967277 - song.with([.albums], preferredSource: .library) generates thousands of lines of EntityQueries in the console I’ve noticed that when using the preferredSource: .library when requesting additional properties on a library item creates ~6,000 of “EntityQuery” entries in the console, all in the span of a second. This doesn’t seem to be leading to any major performance issues, but it sure seems like something isn't right. let request = MusicLibraryRequest<Song>.init() do { let response = try await request.response() guard let song = response.items.first else { return } let songWithAlbums = try await song.with([.albums], preferredSource: .library) } catch { print(error) } generates the following output (except... 6,000 of them) 2022-07-31 13:02:07.729003-0400 MusicKitFutzing[9405:2192606] [EntityQuery] Finished fetching results in 0s 2022-07-31 13:02:07.729047-0400 MusicKitFutzing[9405:2192605] [EntityQuery] Finished executing query in 0.00100017s 2022-07-31 13:02:07.729202-0400 MusicKitFutzing[9405:2192611] [EntityQuery] Finished executing query in 0s 2022-07-31 13:02:07.729240-0400 MusicKitFutzing[9405:2192605] [EntityQuery] Finished fetching results in 0s
1
1
1.1k
2w
MusicLibraryRequest to get all tracks from a playlist (iOS 16 beta)
Hi there, tl;dr: What's the best way to get all tracks (with catalog IDs) from a playlist that has more than 100 tracks, using MusicLibraryRequest. I'm doing something dumb, not understanding something, and possibly both. I've got an existing, kinda okay function that uses the MusicDataRequest and the Apple Music API to fetch all tracks from a playlist, with pagination like this: func getTracksFromAppleMusicLibraryPlaylist(playlist: AppleMusicPlaylist) async throws -> [MusicKit.Track]? {     var tracksToReturn: [MusicKit.Track] = []     var libraryTracks: [AppleMusicLibraryTrack] = []     @Sendable     func fetchTracks(playlist: AppleMusicPlaylist, offset: Int) async -> AppleMusicPlaylistFetchResponse? {         do {             let playlistId = playlist.id             var playlistRequestURLComponents = URLComponents()             playlistRequestURLComponents.scheme = "https"             playlistRequestURLComponents.host = "api.music.apple.com"             playlistRequestURLComponents.path = "/v1/me/library/playlists/\(playlistId)/tracks"             playlistRequestURLComponents.queryItems = [                 URLQueryItem(name: "include", value: "catalog"),                 URLQueryItem(name: "limit", value: "100"),                 URLQueryItem(name: "offset", value: String(offset)),             ]             if let playlistRequestURL = playlistRequestURLComponents.url {                 let playlistRequest = MusicDataRequest(urlRequest: URLRequest(url: playlistRequestURL))                 let playlistResponse = try await playlistRequest.response()                 let decoder = JSONDecoder()                 // print("Get Tracks Dump")                 // print(String(data: playlistResponse.data, encoding: .utf8)!)                            let response = try decoder.decode(AppleMusicPlaylistFetchResponse.self, from: playlistResponse.data)                 return response             } else {                 print("Bad URL!")             }         } catch {             print(error)         }         return nil     }     Logger.log(.info, "Fetching inital tracks from \(playlist.attributes.name)")     if let response = await fetchTracks(playlist: playlist, offset: 0) {         if let items = response.data {             libraryTracks = items         }         if let totalItemCount = response.meta?.total {             Logger.log(.info, "There are \(totalItemCount) track(s) in \(playlist.attributes.name)")             if totalItemCount > 100 {                 let remainingItems = (totalItemCount - 100)                 let calls = remainingItems <= 100 ? 1 : (totalItemCount - 100) / 100                 Logger.log(.info, "Total items: \(totalItemCount)")                 Logger.log(.info, "Remaining items: \(remainingItems)")                 Logger.log(.info, "Calls: \(calls)")                 await withTaskGroup(of: [AppleMusicLibraryTrack]?.self) { group in                     for offset in stride(from: 100, to: calls * 100, by: 100) {                         Logger.log(.info, "Fetching additional tracks from \(playlist.attributes.name) with offset of \(offset)")                         group.addTask {                             if let response = await fetchTracks(playlist: playlist, offset: offset) {                                 if let items = response.data {                                     return items                                 }                             }                             return nil                         }                     }                     for await (fetchedTracks) in group {                         if let tracks = fetchedTracks {                             libraryTracks.append(contentsOf: tracks)                         }                     }                 }             }         }     } // props to @JoeKun for this bit of magic     Logger.log(.info, "Matching library playlist tracks with catalog tracks...")     for (i, track) in libraryTracks.enumerated() {         if let correspondingCatalogTrack = track.relationships?.catalog?.first {             tracksToReturn.append(correspondingCatalogTrack)             print("\(i) => \(track.id) corresponds to catalog track with ID: \(correspondingCatalogTrack.id).")         } else {             Logger.log(.warning, "\(i) => \(track.id) doesn't have any corresponding catalog track.")         }     }     if tracksToReturn.count == 0 {         return nil     }     return tracksToReturn } While not the most elegant, it gets the job done, and it's kinda quick due to the use of withTaskGroup .esp with playlists containing more than 100 songs/tracks. Regardless, I'm kinda stuck, trying to do something similar with the new MusicLibraryReqeust in iOS 16. The only way I can think of to get tracks from a playlist, using MusicLibraryRequest, having read the new docs, is like this: @available(iOS 16.0, *) func getAllTracksFromHugePlaylist(id: MusicItemID) async throws -> [MusicKit.Track]? {     do {         var request = MusicLibraryRequest<MusicKit.Playlist>()         request.filter(matching: \.id, equalTo: id)         let response = try await request.response()         if response.items.count > 0 {             if let tracks = try await response.items[0].with(.tracks, preferredSource: .catalog).tracks {                 Logger.log(.info, "Playlist track count: \(tracks.count)")                 return tracks.compactMap{$0}             }         }     } catch {         Logger.log(.error, "Could not: \(error)")     }     return nil } The problem with this is that .with seems to be capped at 100 songs/tracks, and I can't see any way to change that. Knowing that, I can't seem to tell MusicLibraryRequest that I want the tracks of the playlist with the initial request, where before I could use request.properties = .tracks, which I could then paginate if available. Any help setting me on the right course would be greatly appreciated.
8
0
2.4k
Sep ’23
Editing a Library Playlist (MusicKit: iOS 16 beta)
I've just begun to dip my toes into the iOS16 waters. One of the first things that I've attempted is to edit a library playlist using: try await MusicLibrary.shared.edit(targetPlaylist, items: tracksToAdd) Where targetPlaylist is of type MusicItemCollection<MusicKit.Playlist>.Element and tracksToAdd is of type [Track] The targetPlaylist was created, using new iOS16 way, here: let newPlaylist = try await MusicLibrary.shared.createPlaylist(name: name, description: description) tracksToAdd is derived by performing a MusicLibraryRequest on a specific playlist ID, and then doing something like this: if let tracksToAdd = try await playlist.with(.tracks).tracks {    // add tracks to target playlist } My problem is that when I perform attempt the edit, I am faced with a rather sad looking crash. libdispatch.dylib`dispatch_group_leave.cold.1:     0x10b43d62c <+0>:  mov    x8, #0x0     0x10b43d630 <+4>:  stp    x20, x21, [sp, #-0x10]!     0x10b43d634 <+8>:  adrp   x20, 6     0x10b43d638 <+12>: add    x20, x20, #0xfbf          ; "BUG IN CLIENT OF LIBDISPATCH: Unbalanced call to dispatch_group_leave()"     0x10b43d63c <+16>: adrp   x21, 40     0x10b43d640 <+20>: add    x21, x21, #0x260          ; gCRAnnotations     0x10b43d644 <+24>: str    x20, [x21, #0x8]     0x10b43d648 <+28>: str    x8, [x21, #0x38]     0x10b43d64c <+32>: ldp    x20, x21, [sp], #0x10 ->  0x10b43d650 <+36>: brk    #0x1 I assume that I must be doing something wrong, but I frankly have no idea how to troubleshoot this. Any help would be most appreciated. Thanks. @david-apple?
10
0
2.7k
Sep ’23
Thoughts on MusicLibraryRequest as a replacement for MPMediaQuery
I'm very excited about the new MusicLibrary API, but after a couple of days of playing around with it, I have to say that I find the implementation of filtering MusicLibraryRequests a little confusing. MPMediaQuery has a fairly extensive list of predicates that can be applied, including string and persistentID comparisons for artist, album artist genre, and more. It also lets you filter on an item’s title. MusicLibraryRequests let you filter on the item’s ID, or on its MusicKit Artist and Genre relationships. To me, this seems like it adds an extra step.  With an MPMediaQuery, if I wanted to fetch every album by a given artist, I’d apply an MPMediaPropertyPredicate looking at MPMediaItemPropertyAlbumArtist and compare the string. It was also easy to change the MPMediaPredicateComparison to .contains to match more widely. If I wanted to surface albums by “Aesop Rock” or “Aesop Rock & Blockhead,” I could use that. In the MusicLibraryRequest implementation, it looks like I need to perform a MusicLibraryRequest<Artist> first in order to get the Artist objects. There’s no filter for the name property, so if I don’t have their IDs, I’ve got to use filter(text:). From there, I can take the results of that request and apply them to my MusicLibraryRequest<Album> using the filter(matching:memberOf) function.  I could use filter(text:) on the MusicLibraryRequest<Album>, but that filters across multiple properties (title and artistName?) and is less precise than defining the actual property I want to match against. I think my ideal version of the MusicLibraryRequest API would offer something like filter(matching:equalTo:) or filter(matching:contains:) that worked off of KeyPaths rather than relationships. That seems more intuitive to me. I’m not saying we need every property from every filterable MPMediaItemProperty key, but I’d love to be able to do it on title, artistName, and other common metadata. That might look something like: filter(matching: \.title, contains: “Abbey Road”) filter(matching: \.artistName, equalTo: “Between The Buried And Me”) I noticed that filter(text:) is case insensitive, which is awesome, and something I’ve wanted for a long time in MPMediaPropertyPredicate. As a bonus, it would be great if a KeyPath based filter API supported a case sensitivity flag. This is less of a problem when dealing with Apple Music catalog content, but users’ libraries are a harsh environment, and you might have an artist “Between The Buried And Me” and one called “Between the Buried and Me.” It would be great to get albums from both with something like: filter(matching: \.artistName, equalTo: “Between The Buried And Me”, caseSensitive: false)  I've submitted the above as FB10185685. I also submitted another feedback this morning regarding filter(text:) and repeating text as FB10184823. My last wishlist item for this API (for the time being!) is exposing the MPMediaItemPropertyAlbumPersistentID as an available filter attribute. I know, I know… hear me out. If you take a look at the other thread I made today, you’ll see that due to missing metadata in MusicKit, I still have some use cases where I need to be able to reference an MPMediaItem and might need to fetch its containing MPMediaItemCollection to get at other tracks on the album. It would be nice to seamlessly be able to fetch the MPMediaItemCollection or the library Album using a shared identifier, especially when it comes to being able to play the album in MusicKit’s player rather than Media Player’s.  I've submitted that list bit as FB10185789 Thanks for bearing with my walls of text today. Keep up the great work!
10
1
2.8k
Jun ’24