Post not yet marked as solved
Hi there,
This is more of a "how-to," "am I doing this right?" question, as I've never done this before, and I couldn't find any definitive write up for how to do this, so I'm asking here.
With the release of iOS16 beta, I've been implementing some new MusicKit features in an iOS app I'm working on that was initially built for iOS 15.
Setup:
dev machine: masOS Monterey 12.4
test device 1: iOS 15.4
test device 2: iOS 16 beta 2
Xcode versions:
14.0 beta 2 (14A5229c)
13.4.1 (13F100)
The original app was written, using Xcode 13 and has an iOS Development Target of 15.0
Steps:
create new branch off main called beta16
open Xcode beta2 and switch to new branch
set the iOS Development Target for the project to 15.0
make code changes in the new branch, using ifavailable to handle both iOS 16 and fallback version code.
Results:
When I test the new code using Xcode 14 beta 2 on an iOS 16 device, things work as expected.
When I test the new code using Xcode 14 beta on an iOS 15 device, the app builds and then crashes immediately upon open with:
dyld[38909]: Symbol not found: _$s8MusicKit0A20CatalogSearchRequestV17includeTopResultsSbvs
Referenced from: /private/var/containers/Bundle/Application/4BB8F74F-FDA6-4DF1-8B04-010EA87BA80C/MyApp.app/MyApp
Expected in: /System/Library/Frameworks/MusicKit.framework/MusicKit
Symbol not found: _$s8MusicKit0A20CatalogSearchRequestV17includeTopResultsSbvs
Referenced from: /private/var/containers/Bundle/Application/4BB8F74F-FDA6-4DF1-8B04-010EA87BA80C/MyApp.app/MyApp
Expected in: /System/Library/Frameworks/MusicKit.framework/MusicKit
When coding, I followed all of Xcodes prompting that says when a potentially unsupported new feature is being used.
When I look to where .includeTopResults is being used, I can see that it was not wrapped with ifavailable:
var request = MusicCatalogSearchRequest(term: searchString, types: [Song.self])
request.includeTopResults = true
let response = try await request.response()
If I comment out the line with .includeTopResults, the app runs on the iOS 15 device w/o issue.
If I wrap it in ifavailable, like this, it crashes as before:
var request = MusicCatalogSearchRequest(term: searchString, types: [Song.self])
if #available(iOS 16, *) {
request.includeTopResults = true
}
let response = try await request.response()
If I check the docs here, I think I'm using .includeTopResults correctly.
Question:
My main question here is if I'm correctly developing the app toward the goal of supporting new iOS 16 features, while also supporting devices using iOS 15, via fallback.
Ultimately, I plan to merge the beta16 branch into a branch from which I'll deploy the app.
The app crashing when using .includeTopResults has me hesitant because I don't know if that's a bug or if I'm the bug. Usually, it's the latter.
It seems like the steps outlined above are the correct way to develop toward supporting upcoming features and "legacy iOS versions", but I'm not sure.
Any clarity on this would be most appreciated.
Thanks!
Hi there!
I was experimenting with the MusicCatalogChartsRequest. I have one doubt, and I do not know if it is the intended behavior.
If I have the kinds as [.cityTop] and the types as [Song.self], why does it print the city playlists and the top played playlists?
Ideally, it should print an empty array, right? As I have only Song as a type?
var request = MusicCatalogChartsRequest(kinds: [.cityTop], types: [Song.self])
request.limit = 1
let response = try await request.response()
print(response.playlistCharts)
The output is:
[MusicCatalogChart<Playlist>(
id: "city-top:cityCharts",
kind: .cityTop,
title: "City Charts",
items: [
Playlist(
id: "pl.db537759ae3341eaa600bc5482209f7c",
name: "Top 25: Mumbai",
curatorName: "Apple Music",
isChart: true,
kind: "editorial",
lastModifiedDate: "2022-06-21",
url: "https://music.apple.com/in/playlist/top-25-mumbai/pl.db537759ae3341eaa600bc5482209f7c"
)
],
hasNextBatch: true
), MusicCatalogChart<Playlist>(
id: "most-played:playlists",
kind: .mostPlayed,
title: "Top Playlists",
items: [
Playlist(
id: "pl.f4d106fed2bd41149aaacabb233eb5eb",
name: "Today’s Hits",
curatorName: "Apple Music Hits",
isChart: false,
kind: "editorial",
lastModifiedDate: "2022-06-21",
shortDescription: "Drake gets loose with “Sticky”.",
standardDescription: "Among the many surprises of Drake’s <i>Honestly, Nevermind</i>—not the least of which is the album’s very existence—is that it isn’t really a hip-hop album at all; he’s mainly singing. But “Sticky” is one of the few tracks where the project’s commitment to exploring dance music—courtesy here of production work from Australian artist RY X and Gordo (aka DJ Carnage)—meets actual rapping. Add Today’s Hits to your library to stay up on the biggest songs in pop, hip-hop, R&B and more.",
url: "https://music.apple.com/in/playlist/todays-hits/pl.f4d106fed2bd41149aaacabb233eb5eb"
)
],
hasNextBatch: true
)]
Thank you!
Post not yet marked as solved
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.
Post not yet marked as solved
Hello,
I'm new to the Swift MusicKit API and am starting with the implementation in iOS 16.
I'm getting stuck on an issue where there is no background or text color associated with the Artwork object. Is this something you have to make an additional property request for, and if so, how do you do that?
var catalogSearch = MusicCatalogResourceRequest<Album>(matching: \.id, equalTo: item.id)
let catalogResponse = try await request.response()
guard let firstItem = catalogResponse.items.first else {
return
}
In this example, firstItem.artwork only contains the url and what look like incorrect max width/height values.
here's a printout of firstItem.artwork
Optional(Artwork(
urlFormat: "musicKit://artwork/library/5F37858D-F46B-4F12-BA67-40FA8DD63D87/{w}x{h}?at=item&fat=&id=7718670444435992305&lid=5F37858D-F46B-4F12-BA67-40FA8DD63D87&mt=music&aat=Music122/v4/37/25/f5/3725f515-249f-7b91-77bb-f479cd48201c/22UMGIM32254.rgb.jpg",
maximumWidth: 0,
maximumHeight: 0
))
Post not yet marked as solved
Do we have any development allowance/tools for playback from Apple Music without a necessity to pay for the subscription?
Post not yet marked as solved
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?
I am making a media player for Apple iOS and wanted it to be able to pull my music library using an API. The closest thing I found to this is this:
https://developer.apple.com/musickit/
However, I don't know if it will allow users to play their music purchased by apple. Does anyone know how to do this so the user logs into their apple account through my app and then has complete access to their movies, tv shows, and music?
Any answers are really appreciated I did my best to find the answer to this and know its annoying to ask these questions when google or apple has it documented somewhere.
Hi there!
I was using the Apple Music app and stumbled upon Categories. For example, if you search for Apple Music Party, the first result is a Category that shows a list of playlists.
How do I access these using MusicKit? Is Categories Music exclusive only or available to third-party developers?
Post not yet marked as solved
Hi there,
I can see dateAdded when looking at the response of a get library playlist request via the Apple Music API, but this does not appear to exist on any tracks within a given library playlist.
I'm assuming that this just isn't there, but I'm asking here, in case I've missed it.
Thanks.
Post not yet marked as solved
Hi Apple Engineers,
In my app Caset, the album artwork is no longer displaying correctly, instead the console is printing with error:
[Artwork] [MPArtwork] Failed to create directory at 'file:///var/mobile/Media/iTunes_Control/iTunes/Artwork/Caches/96x96/61/' with error: Error Domain=NSCocoaErrorDomain Code=513 "You don’t have permission to save the file “61” in the folder “96x96”." UserInfo={NSFilePath=/var/mobile/Media/iTunes_Control/iTunes/Artwork/Caches/96x96/61, NSUnderlyingError=0x280552dc0 {Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted"}}
Here is the code I am using to display the album artwork of the current item:
@ObservedObject private var playerQueue = ApplicationMusicPlayer.shared.queue
//In the BODY
switch playerQueue.currentEntry?.item {
case .song(let song):
if let artwork = song.artwork {
ArtworkImage(artwork, width: 40, height: 40)
}
case .musicVideo(let video):
if let artwork = video.artwork {
ArtworkImage(artwork, width: 70, height: 40)
}
case nil:
EmptyView()
}
Related bug has been filed here: FB10189431
This unexpected behavior is new and was previously worked as expected in early versions of iOS 15.
Post not yet marked as solved
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!
Post not yet marked as solved
I have been excitedly testing out MusicKit's new features to interact with a user's music library over the last couple of days. Albums and Songs are a much nicer paradigm than MPMediaItemCollection and MPMediaItem, but I'm afraid that there are some library-focused metadata options present on MPMediatem that are seemingly not currently exposed on Songs fetched from a MusicLibraryRequest that force me back into the world of MPMediaItems.
A big component of my app is being able to sort and filter your library across properties like play count and date added. Am I right that these properties are not available when interacting with library Songs in MusicKit?
dateAdded - The date the item was added to the library
lastPlayTime - The date on which the item was last played
playCount - The number of times the item has been played
For as long as those properties are not available, I will need to dip back into MPMediaItems in order to continue offering important pieces of functionality in my app.
Those three are my highest priority for this area of MusicKit at this time, and are my biggest blockers on jumping into the new world as opposed to staying the old. I've submitted the above as FB10185523
While I'm at it, I also have a wishlist of some additional properties, only one of which is currently exposed on MPMediaItem. The rest of which correspond to metadata a user can set in Music.app on the desktop but have not historically been publicly exposed on MPMediaItem:
isCloudItem - This is currently exposed on MPMediaItem and tells you whether an item is locally downloaded on the device. I understand that the includeOnlyDownloadedContent option can be set on a MusicLibraryRequest to return locally downloaded devices, but it would also be nice to have on a per-song basis to be able to display an indicator to the user.
startTime and stopTime - These properties correspond to the custom “start” and “stop” times a user can define for a song on the Options tab of Music.app on the desktop. These have not historically been exposed publicly. It would be nice to have access to these in order to be able to display an accurate play time if a user has customized it.
sortAlbumTitle, sortAlbumArtist, sortArtist - These properties correspond to the “sort as” options a user can define for a song on the Sorting tab of Music.app on the desktop. These have not historically been exposed publicly. It would be nice to have access to these in order to display library items to a user in the order they’ve defined and expect, rather than ignore their preference.
I've submitted the above as FB10185575
Thanks!
Hi there,
I just watched the WWDC22 vids on MusicKit enhancements.
In the Explore More Content with MusicKit video, there is a brief mention of "edit playlists" at 24:33.
I asked about this a couple years ago here in this forum.
The ever-helpful @JoeKun suggested that I submit a Feedback Assistant ticket on the need/justification for this feature, which I did some time ago: FB9798928. Sadly, there's been no traction on that.
I'm super hopeful that the ability to remove tracks from a library playlist is now, or at least will soon be, possible; however, I can't find anything in the docs yet to support this notion.
Am I being overly optimistic, or are we finally going to get this much requested feature?
Thanks.
Post not yet marked as solved
Hello!
When creating a playlist we are getting an id which is the one in the user's library, it's not enough to create a link and share it with friends for example.
To do so, we need a global id. The only way I found to generate this global id is by clicking on the share button of the playlist on Apple Music. After doing this manual action, the global id is properly returned by the Playlist endpoint on the Apple Music API.
I wanted to know if there was a endpoint that could be called in order to generate this global id without having to go on the Apple Music app?
Thanks for your help
Post not yet marked as solved
Hello, I have a question regarding the update of playlists through the API. I haven't found this documented, but I prefer asking just in case I missed something.
Is there an endpoint/parameter to add tracks to a playlist by overriding the tracks that are in it?
I only found an endpoint to append tracks to a playlist, which is not enough for how my app works
Thanks!
Post not yet marked as solved
Hello, I'm using the Apple Music API to store tracks & albums links in my database and I need a way to determine if a track/album is available for purchase on iTunes or if it's a streaming one.
I'm currently using the "playParams" attribute to determine it. If it is present I consider the track to be streaming, if it's not, I consider it to be a purchasable track.
I'm not sure if this is the best way to do so, I'd gladly take your recommendations, maybe there's an attribute I missed on the API.
Post not yet marked as solved
Hello, with the recent update of the Apple Music API allowing to retrieve artwork for artists, I wanted to know if it was possible to post an artwork when creating a playlist through the API? If so, what is the format and parameters to use and is it documented somewhere?
Thanks for your help
Post not yet marked as solved
Hello,
MusicPropertySource.catalog says it is available in the docs on macOS, but .library is not. Is that an oversight? How does this integrate with current uses of iTunesMusicLibrary framework and ITLibrary?
Thank you,
-- Greg Bolsinga
Post not yet marked as solved
I had just updated to IOS 16 beta and I noticed that the recently added songs get skipped when you set them to "play next", it's not happening with older songs just ones I've added recently.
This is on a iPhone 12 Pro Max
Hey there Apple Music team! I'm excited to dig into the sessions coming up this week, and what I've seen so far from the developer documentation diffs looks great: audio quality, artist images, and a way to interface with a user's music library in MusicKit. Love it!
The thing at the very top of my WWDC wishlist this year was macOS/Mac Catalyst support for the ApplicationMusicPlayer class. I just got finished installing Ventura and Xcode 14, and sadly it looks like the support story is the same as on Big Sur. No API availability on macOS, and an available Mac Catalyst API that ultimately results in the same error from a feedback I submitted on Big Sur: FB9851840
The connection to service named com.apple.Music.MPMusicPlayerApplicationControllerInternal was invalidated: failed at lookup with error 3 - No such process.
Is that the end of the story on Ventura, or is there a chance support might be added in a later beta? Is there any additional detail at all that can be shared? I field several requests a week asking if/when my app is coming to the Mac, and I would really love to be able to make that happen. If there is anything at all I can do to test and help overcome the engineering challenges alluded to in the past, I am ready, willing, and able!
In any case, thanks for the great work, and I'm looking forward to spending time with the new stuff this summer.