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
Replies
Boosts
Views
Activity
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
}
Here's something similar to the above, but using .moreByCurator, though I'm not sure if I'm using it right, as I never get results.
@available(iOS 15.4, *)
func getCuratorPlaylistsFromPlaylist(playlistId: String) async throws -> String? {
do {
let request = MusicCatalogResourceRequest<MusicKit.Playlist>(matching: \.id, equalTo: MusicItemID(playlistId)) //catalogId
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, and curatorName
Logger.log(.info, "Type of Item: \(type(of: item))") // type is Playlist
let morePlaylists = item.moreByCurator // not sure this is the right way to use .moreByCurator
Logger.log(.info, "More Playlists: \(String(describing: morePlaylists))") // This is always nil!
}
} catch {
// handle error
Logger.log(.error, "Could not findCuratorOfPlaylist \(error)")
}
return nil
}
Trying some more things, admittedly not knowing what I'm doing:
This still requires me to know the catalogId of a playlist that exists as a catalog resource, but it get's me the curatorId, which seems hopeful.
My problem is searching for the curator using a MusicCatalogResourceRequest yields no results.
func findCuratorOfPlaylist(playlistId: String) async throws -> String? {
do {
var requestURLComponents = URLComponents()
requestURLComponents.scheme = "https"
requestURLComponents.host = "api.music.apple.com"
requestURLComponents.path = "/v1/catalog/us/playlists/\(playlistId)/curator" // Got this idea from @snuff4
if let url = requestURLComponents.url {
let dataRequest = MusicDataRequest(urlRequest: URLRequest(url: url))
let dataResponse = try await dataRequest.response()
let decoder = JSONDecoder()
let response = try decoder.decode(AppleMusicGetCuratorResponse.self, from: dataResponse.data)
Logger.log(.info, "Curator Response: \(response)") // this returns the id, name, and kind (.editorial)
let items = response.data
let id = items[0].id
Logger.log(.info, "Searching for Curator with ID of \(id)")
let request = MusicCatalogResourceRequest<Curator>(matching: \.id, equalTo: id)
let resp = try await request.response()
Logger.log(.info, "Find Curator Request Response Items: \(resp.items)") // MusicCatalogResourceResponse<Curator>()
Logger.log(.info, "Is Curator Request Response empty: \(resp.items.isEmpty)") // true
}
} catch {
// handle error
Logger.log(.error, "Could not findCuratorOfPlaylist \(error)")
}
return nil
}
Thanks for the pro follow-up, @JoeKun.
I've been dabbling, and it looks like add(to:) is singular, while edit would allow me to achieve my use case.
Nice to see these new features coming to MusicKit!
FYI: @dutton @JoeKun,
After testing, the problem still exists, and I've updated FB9857866 accordingly.
UPDATE!
It appears that this is now possible.
See here!
@david-apple This is great!
I'm so happy that this feature is now available... it's been a long time coming.
It appears that I need to target iOS16, which means I gotta update my test device, which is an iPod touch generation 7.
I look forward to getting into this soon.
@david-apple Awesome!
Does this mean that only playlists created with the new [createPlaylist](@david-apple Awesome!) function are editable, or can playlists created via the Apple Music API also be edited?
Thanks,
Kim
This is wild. For the first time in an exceptionally long time, I actually got a response on the Feedback Assistant regarding an issue I opened (thanks @JoeKun!).
They wanted me to reproduce the problem, which I had painstakingly done in text in the ticket.
Regardless, I decided to make a screen cast of the issue, so as to illustrate the issue, and...
The problem is magically gone!
I do need to say that this WAS a problem, and I swear it was still happening within the last month (I've been busy on other stuff, so I can't be sure of when it last didn't work).
That said, @dutton, can you please check on your side to see if this issue has been resolved? Much appreciated!
Imma gonna keep testing, but if this keeps working, I'll close this issue once confirmed.
@JoeKun, thanks for following up. I think maybe mentions in comments don't send notifications. Not sure.
Anyhow, please keep me posted, and this is still an issue as of recently.
I have confirmed that the behavior is related to country/storefront by programatically saving the offending track to a playlist in Apple Music and attempting to play it there.
The result of that is an alert that says, "This song is not currently available in your country or region."
So my quesiton still stands: "How can I create a MusicCatalogRequest in such a way that I only get results that are playable in my country/storefront?"
Hi @eskimo,
Thanks for the pro follow up. That is correct.
To clarify further, getUpdateAppData() function is run after manually invoking the objc command in Xcode. I don't actually know of another way to test this.
The task is initially registered with the configureBackgroundTasks() function (see last code block in my prior post) in the init function of the app's app file (not app delegate).
Kindly let me know if you have any further questions.
HI @eskimo,
Thanks for chiming in and providing insight.
Using the below, I set a breakpoint right when the app is moved to the background (I'm not using appDelegate).
.onReceive(NotificationCenter.default.publisher(for: UIApplication.didEnterBackgroundNotification)) { _ in
backgroundTaskManager.scheduleAppRefresh() // <= breakpoint here
}
Once the app is paused, in Xcode, I run the following command in the Xcode terminal:
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.example.task"]
That responds with:
2021-12-07 05:29:25.405222+0700 App[602:49934] Simulating launch for task with identifier com.example.task
After that, I resume the app in Xcode and see (I believe "missing data" is not an error below):
2021-12-07 05:29:29.724492+0700 App[602:50205] Starting simulated task: <decode: missing data>
Followed by the initial operation of the getUpdateAppData() function being called.
The initial operation is a function that runs an Amplify (iOS library) graphQL query (here's an excerpt):
func getMyFriends(currentUser: User) async throws -> [Friend] {
Logger.log(.fetch, "Fetching \(currentUser.username)'s friends...")
return try await withCheckedThrowingContinuation { continuation in
let friend = Friend.keys
let predicate = friend.owner == currentUser.username
Amplify.API.query(request: .list(Friend.self, where: predicate)) { event in
switch event {
case .success(let result):
...
The app seems to just stop right after the logging of "Fetching (currentUser.username)'s friends..."
No errors are thrown that I can see.
Note this function works fine when it's called when the app is in the foreground.
Also, when I return the app to the foreground, the getUpdateAppData() tries to resume, and rarely (if ever) completes as it should:
🐕 Fetching Elmer's friends...
2021-12-07 06:02:20.150514+0700 App[654:65556] Connection 1: encountered error(1:53) // <= upon return to foreground
2021-12-07 06:02:20.150650+0700 App[654:65556] Connection 1: received failure notification
2021-12-07 06:02:20.151257+0700 App[654:65556] [connection] nw_connection_copy_connected_local_endpoint [C1] Connection has no connected path
2021-12-07 06:02:20.151450+0700 App[654:65556] [connection] nw_connection_copy_connected_remote_endpoint [C1] Connection has no connected path
2021-12-07 06:02:20.153068+0700 App[654:65556] Connection 2: encountered error(1:53)
2021-12-07 06:02:20.153125+0700 App[654:65556] Connection 2: received failure notification
2021-12-07 06:02:20.153280+0700 App[654:65556] [connection] nw_connection_copy_connected_local_endpoint [C2] Connection has no connected path
2021-12-07 06:02:20.153891+0700 App[654:65556] [connection] nw_connection_copy_connected_remote_endpoint [C2] Connection has no connected path
2021-12-07 06:02:20.155082+0700 App[654:65556] Connection 3: encountered error(1:53)
2021-12-07 06:02:20.155293+0700 App[654:65556] Connection 3: received failure notification
2021-12-07 06:02:20.161795+0700 App[654:65556] [connection] nw_connection_copy_connected_local_endpoint [C3] Connection has no connected path
2021-12-07 06:02:20.161877+0700 App[654:65556] [connection] nw_connection_copy_connected_remote_endpoint [C3] Connection has no connected path
🦴 Successfully fetched Elmer's friends!
...
Maybe Amplify doesn't work when in the background? I dunno.
One thing that I have yet to try is testing with a simple, single API operation in lieu of the somewhat more involved getUpdateAppData() function that is currently running through completion.
Any further thoughts and/or advice is appreciated.
private func configureBackgroundTasks() {
Logger.log(.info, "Registering background tasks...")
let bgTaskIdentifier = "com.example.task"
BGTaskScheduler.shared.register(forTaskWithIdentifier: bgTaskIdentifier, using: DispatchQueue.main) { (task) in
Logger.log(.info, "Performing background task \(bgTaskIdentifier)")
Task {
if let updatedData = await appManager.getUpdateAppData() {
DispatchQueue.main.async {
appManager.data = updatedData
task.setTaskCompleted(success: true)
}
} else {
task.setTaskCompleted(success: false)
}
backgroundTaskManager.scheduleAppRefresh()
}
}
}
Hi @OOPer,
Thanks much for taking the time to respond.
I have tried several variations, in an attempt to get this working.
The result from your suggested mods: using Task instead of Task.init and putting task.setTaskCompleted(success: true) in a DispatchQueue.main.async {}, results in:
2021-12-04 14:58:39.136393-0800 App[8341:2562502] [connection] nw_read_request_report [C7] Receive failed with error "Socket is not connected"
2021-12-04 14:58:39.139668-0800 App[8341:2562502] [connection] nw_read_request_report [C7] Receive failed with error "Socket is not connected"
Doing the same, but using Task.init {... results in:
2021-12-04 15:03:53.128265-0800 App[8344:2563988] [Framework] Task <BGAppRefreshTask: com.example.task> dealloc'd without completing. This is a programmer error.
2021-12-04 15:03:53.128610-0800 App[8344:2563988] Marking simulated task complete: (null)
I do like the very specific calling out the programmer error 😄
I've also tried using BGProcessingTaskRequest instead of BGAppRefreshTaskRequest just in case the update operation was timing out after 30 seconds, which I believe is the time window alloted for a refresh task.