Streaming is available in most browsers,
and in the Developer app.
-
Explore more content with MusicKit
Discover how you can enhance and personalize your app using MusicKit. We'll take you through the latest additions to the MusicKit framework and explore how you can bring music content to your app through requests, metadata, and more.
Resources
Related Videos
WWDC22
WWDC21
-
Download
♪ ♪ Hi, and welcome to WWDC. My name is David, and I'd like to tell you about how you can explore more content with MusicKit. The MusicKit framework was launched in 2021, providing a set of APIs to access and play music natively in Swift. This framework makes it easy for your app to integrate with Apple Music, providing access to the entire Apple Music catalog. Today, I'd like to talk about some of the major enhancements we've made to MusicKit. I'll start off with some additions to get even more out of the Apple Music catalog, with new music item types, new requests, and new metadata.
After that, I'll discuss how you can fetch personalized content to provide a tailored experience for each of your users. Next up, I'll go beyond the Apple Music catalog. This year, you can take your app to a whole new level by including music from your user's library. Finally, I'll discuss how to actively interact with the library, such as adding items to the library or a playlist, as well as creating and editing playlists. Let's dive into the catalog content additions. The initial release of MusicKit introduced a new model layer for music, including core types like Songs, Albums, and Playlists. This year, we're making it easier to discover new music with MusicKit with the addition of two new types: Curator and Radio Show.
Beyond that, MusicKit now also allows you to build great UI for searching through the catalog, access top charts to get the most popular songs, albums, and more, and fetch new attributes such as higher quality audio metadata like Spatial Audio with Dolby Atmos.
Let's start with curators and radio shows, which are fantastic resources for music discovery. Here, we're taking a look at an example Curator, Nike. Other examples of curators are Shazam and Beats by Dr. Dre. Here, we can easily find all of the playlists generated by this curator. This functionality allows people to get quick access to playlists they may love, finding new songs or revisiting old favorites. Now let's dive into the more technical details.
Curators host a variety of attributes. Some of the primary attributes of this new Curator type are name, url, artwork, and kind. The kind property is an enum that can either be "editorial" or "external" indicating whether a given curator is an Apple curator or a third party curator.
Curators also have a playlists relationship showing playlists made by that curator, truly servicing the music discovery notion we just saw.
Next, we have the Radio Show type. Radio Shows like "New Music Daily by Zane Lowe" and "Pop Hits Radio by Brooke Reese" are another way to discover new music through seasoned professionals. Much like the Curator type, radio shows also have a playlists relationship to find the music a radio show features. Just as these two new types hold relationships to playlists, we are also exposing two new relationships on the Playlist type, "Curator" and "RadioShow" for the reversed logic: that given a playlist, we can easily get the structure of which entity generated said playlist.
MusicKit allows searching the catalog for content from a plethora of types, and now we're adding support for our new item types, like curators and radio shows. The list just continues to grow, and as a result, building good UI becomes more and more challenging. That's why this year, we're making it much easier for you through top results and suggestions. Let's take a look at the utility these enhancements bring to a UI. When typing to search for content, you may want to provide strong, music-related auto-complete support. That's where suggestions come into play, providing terms that people may be trying to reach. You can even take it a step further and display top results for quick access to what people may be searching for. For the best results, you may not care about the type of the item, but instead want to focus on relevancy. This is the power of top results. Now, let's take a look at what implementing this would look like, starting with Top Results. Here we have the existing way to create a catalog search request, requiring a search term and the types of items that you want represented. The response includes collections categorized by the requested types, meaning multiple lists of type specific results. Although this is great, we want to expose a single list of the most relevant results that is type agnostic. And requesting this information is as simple as adding one line. Here we're setting the includeTopResults property on the request to true, and a new property is filled in the response. This new property is named topResults which contains items of any of the requested types. Here's what the output of the print statement looks like. We can see that the top results returned are a mix of songs, artists, and albums in a single collection and is ordered by relevancy. Now I'll show you how to help people get to their search destination faster with Suggestions. Simply create a suggestions request with a string term. Upon calling response, you'll get back a suggestions response. The response contains an array of Suggestions. And each suggestion includes a display term which is suitable for your UI, as well as a search term. When people select a suggestion, you can fetch the corresponding results by performing a search request with the search term.
Catalog charts are a great way to stay up-to-date with the most popular songs. MusicKit offers various types of charts to see what's trending. The types of charts being offered are top charts, such as Top Songs or Top Albums, which correspond to the most played music, city charts, and daily top 100. You may also specify the requested charts to be filtered by a specific genre. Retrieving these charts through code is extremely simple. The catalog charts request follows established patterns already used in the catalog search request. First, initialize a charts request. You can then specify the kinds of charts you'd like. By default, this will fetch the most played content, but you can also include daily global top charts and city top charts. Lastly, just specify the types you want your charts to contain. And that's it. When we access the first playlist chart in the response, we get a MusicCatalogChart representing the daily global top charts. Its items are playlists like "Top 100: Global" and "Top 100: USA". If you've been fetching catalog charts using MusicDataRequest in the past, you no longer need to, as MusicKit will do the work for you, with pagination support built into the collection of items. In 2021, we introduced groundbreaking audio experiences with true multidimensional sound and clarity: Spatial Audio with support for Dolby Atmos. This immersive experience is already available for thousands of songs, and now you can access this data. MusicKit exposes which audio resources are available for any song or album through Audio Variants, so now, you can relay this information to others. Examples of audio variants are the previously mentioned Spatial Audio with Dolby Atmos, Lossless Audio, and much more.
We are also exposing a new boolean property alongside audio variants, is Apple Digital Master, which is the highest quality master supported. Since this metadata is exposed at the item level, audio variants are perfect for a detail view, allowing you to achieve UI like this. Here we have a detail view of an album. And here, we can see the appropriate badges based on the audio variants property mentioned earlier, letting users know what audio quality they can expect. In this case, spatial audio and lossless audio are available for this specific album. Now let's see how we can write code to achieve this. Loading audio variants are like loading any other extended attribute. Take an existing album or song, in this case an album, and use the with method to load the audioVariants extended attribute. Now your detailedAlbum has the audioVariants property populated. Here we can see the audio variants property, which is an array whose element is an AudioVariant. With these values, you can indicate in your UI the available audio resources for that particular element, just like we saw earlier. Now, this is great, but you may want to show these audio badges on more than just top level or detail views. That's why we're also taking it one step further and exposing the active audio variant for the music player. Accessing the active audio variant allows for a visual indication of the quality of audio for the currently playing item, such as Dolby Atmos in this view. And the MusicKit player API automatically chooses the correct audio quality based on user settings and network conditions.
To access the active trait from the player, first, we access the ApplicationMusicPlayer's playback state in an observed object. We can then access the active audioVariant directly from the playback state Now, we simply check the audioVariant property to see if it's dolbyAtmos, and add additional UI if it is. Because the playback state is an observed object, this view will automatically update whenever the currently playing item changes, making sure your view is always up-to-date. Now that we've gone over some catalog additions, let's dive into fetching personalized content. Personalized content is data specific to a subscriber, providing a unique and tailored experience for every user in your app. Normally, personalized content requires special authentications and user tokens, but in the MusicKit framework, we've made this all automatic so you don't have to deal with any of the hassle. The personalized content we're bringing to developers is access to recently played items and personal recommendations. Recently played content is a valuable piece of data for a person's music consumption experience. It allows for quick and easy access to music items you know they enjoy. And when listening to new music, it allows people to later go back and refer to their history. To fetch recently played containers like albums, playlists, and stations, you can create a recently played container request. Note that if you play a song from a playlist or album, the container type will be retrieved. In the response, you will find recently played music items, which offer convenience accessors for the title, subtitle, and artwork. You can also fetch recently played items of more specific types, like songs or stations. Here, we create a recently played request, specifying the Song type through the generic parameter, indicated by the angle brackets. Our response now only contains the songs that we have played. Now, onto personal recommendations. Personal recommendations let your app experience feel more intimate and engaging as they are generated based off the user's library and listening history. Recommendations are nicely organized by themes, resulting in groupings by genres, artists, collections like "Made for You," and more. To fetch personal recommendations, simply create a personal recommendations request. The response is a collection of recommendations. When we log the first recommendation, we can see that this particular element represents the "Made for You" recommendation. Recommendations have an ID, title, and nextRefreshDate. The nextRefreshDate represents the date time for when this recommendation should be refreshed for the most up-to-date suggestions. The playlists property contains all of the playlists that are made for me. Let's take a look at another example of a recommendation. Here we'll print out the second element of the recommendations response. I listen to a ton of alternative music, and this recommendation contains a mix of different types, in this case, Albums and Playlists. Those are grouped in a single collection of items, which are ordered by relevancy, much like top results for catalog search. Now, let's take it a step further and talk about how you can create even more relevant experiences around music by incorporating content from your user's library into your app. This year, MusicKit allows your app to fetch items from the library with two types of requests: the library request and the library sectioned request, search for content in the user's library, and load extended attributes and relationships specifically from the library. Before we see the technical details, let's see how we can use library content to enhance your app. I've been working on a fitness app called Music Marathon that will track your outdoor runs. By incorporating MusicKit in the project, we allow people to play music directly through the app instead of context switching between the Apple Music app and this one. Let's start a new workout and look for music content.
Here we see some recommended playlists retrieved from the personal recommendations request, to give people quick access to playlists we think they'll love. Going to the library tab, we can see it's an empty view. It would be great to be able to see all of my personal playlists, so let's write that feature. I already have some UI set up to handle the basics of this view, and now I want to load the playlists from my library. First, I'll make library request...
Specifying playlist in the generic parameter to indicate that we want the playlists from the user's library.
And I'll store it in a local variable I'll name "request." Next I'll take this request and call the response function.
This method is an async throwing method, so let's add the try and await keywords. and once again store it in a response variable.
Then, I'll update the state object to receive this response.
Now all that's left to do is update the list so we can see the playlists in my UI. I'll iterate through the items in the response using a ForEach...
And retrieve each playlist in the MusicItemCollection.
Now that we have the playlists, I'll pass them into a PlaylistCell I've already made.
Now if we re-run...
And navigate back to the app, we can see all of my personal playlists in the library. Now, I can choose to listen to personal recommendations, anything from the Apple Music catalog, and my own personal library. Now that we've seen how easy it is to access library content, let's look at what else the library request can do. The music library request is a powerful API to fetch items from the user's library. On iOS, unlike other requests to fetch content from the music catalog, MusicLibraryRequest will not actually load data from the network. Instead, it will load items from the copy of the user's library that is stored on device. The basics of this request only require you to specify which music item type is desired. This item type is passed through the generic parameter of the MusicLibraryRequest. You can apply a variety of filters and sort options on the request in order to finely tune your call to match your requirements. This request is also capable of fetching already downloaded content, supporting a fully offline experience. Let's start with the simple, base request, the same request we wrote in the Music Marathon app, but this time, ask for the Albums in the library. The album type is specified through the generic parameter. To perform the request, call the response function. Looking at the output, we have a MusicLibraryResponse, where its items are a MusicItemCollection of all the albums found within the user's music library. Here we notice that these Albums are the same Album structure that you would get from one of our various catalog requests and have the same capabilities. Whereas in this example we are fetching every album in the library, we know there are scenarios where you only want a specific subset of albums. That's why MusicLibraryRequest also enables you to be more specific about what items you want to fetch from the library. Let's take the same request we wrote before and add a filter. Here, we want to load all albums where the isCompilation property is equal to true. When you call filter method, Xcode's autocompletion will only offer specific key paths that are supported for the type of item you are requesting. Now, the response only has albums which are compilations. But that's not all the power the MusicLibraryRequest has to offer. You can chain multiple filters, giving you a more refined request with each addition. What if we wanted all of the compilations of a particular genre? We can add another filter to the request. For example, here we have an instance of Genre named "Dance." You can filter by the genre's relationship to restrict the results to only include compilations that contain this specific genre. Now our response only contains dance compilations. How about only including dance compilations that are already downloaded? To do that, just set the includeOnlyDownloadedContent to true on the request. And that's it. The response is the same MusicLibraryResponse, but the items now only contain elements that are downloaded. As you can see, the music library request is very powerful and unlocks new capabilities that weren't possible with a custom MusicDataRequest. But MusicKit offers even more options to fetch data from the user's library. Meet the Library Sectioned Request. The sectioned request is able to fetch items grouped by sections. As a result, the sectioned request takes in two distinct generic parameters. The first representing the section type, and the second, the item type. The library sectioned request supports the same capabilities as the regular library request, such as a variety of filter and sort methods which you can apply to either the sections or the items. Here's how you can fetch all albums sectioned by their genres using the library sectioned request. The sectioned Response holds a property named "sections" where each element corresponds to the first generic parameter of the request, Genre in this case. Each genre not only exposes its own attributes, but it also contains a collection of albums, accessible via the items property. Those items correspond to the second generic argument. Here, the highlight shows albums whose genre is Alternative. And as mentioned before, the capabilities of filtering and sorting are also available for this sectioned request. Let's say we want the same albums, sectioned by genres, but the albums sorted by their artist's name. We add a sort filter. By specifying the artistName keyPath on Albums and saying that we want these to be ascending, we're sorting the response. Notice that the method is sortItems as we are specifying the sorting to be applied to the items and not the sections. Had we wanted to specify the sections, a set of filterSections and sortSection methods are available. Let's take a look at the new response.
We can now see that our albums are ordered alphabetically by artist name instead of by their titles. Both the library request and library sectioned request are extremely powerful, but you might also want to complement your music search UI by adding search results from the user's library. So we've added a new structured request which operates almost identically to catalog search, but instead of loading results from the catalog, it finds relevant items in the user's library. Just like its catalog counterpart, the library search request only requires a search term and an array of types. Now that we've seen the different ways to retrieve items from the user's library, what about loading extended attributes and relationships? As you may know, the initial release of MusicKit introduced the with method, loading these properties from Apple Music API in a straightforward way. This year, we're augmenting the current with method to also take in a preferred source parameter. This preferred source indicates where to load data from, for extended attributes and relationships that are available in both the Apple Music catalog and the user's library. And for the properties that only live in either the catalog or the library, they will still fetched regardless of the preferred source to make sure nothing is ignored. In addition, you can use this functionality no matter where the initial item came from, whether it be a catalog request, a library request, or elsewhere. It all just works.
Here we have the known way of receiving a relationship of a music item. We're loading the tracks of an album, and when we display the output, we can see all of the tracks for that album. However, with the new addition of the preferredSource property, we can specify that we would like to fetch this relationship from the library. Now our output only contains the tracks of the album found in the library. With the various ways you can now fetch items from the user's library, it only makes sense to allow users to be able to interact with their library directly through MusicKit. Let's jump back into my sample app, Music Marathon, to see some of the capabilities the library offers. As I'm working out, I want to browse through some of my personal recommendations.
As I look through the tracks, I realize that one of these songs would be perfect for my workout playlist. If hold down one of these cells, a contextual menu pops up, allowing me to add this song to a playlist. When we press it, a pop-up of all of my playlists appear again. Let's write code to add the selected track to whichever playlist I pick. I've already piped the selected item to our AddToPlaylistCell cell, so all we have to do is access the MusicLibrary through the shared instance.
We'll call the "add" method, specifying our selected track and which playlist we want to add to.
This method is also an async throwing function, so we add once again the try and await keywords.
Lastly, we'll dismiss the picker by setting the isShowingPlaylistPicker binding variable to false.
Now if we re-run and add a track to a playlist and select one of our playlists, we should expect to see this item added. Navigating back to the library tab within the app, we can see the song is now added to our workout playlist. And that's how simple it is to add an item to a playlist. Let's look at some of the other functionality the library offers. The various other ways to interact with the library are adding content to the library, creating playlists, and editing playlists' metadata and track list. Adding content to the user's music library allows people to find specific songs or albums in the library tab of the Apple Music app, as well as synchronizes across all devices when "Sync Library" is turned on in Settings. Providing this functionality directly in your app saves people from context switching between the Apple Music app and yours, so they can stay engaged in the content you're providing. Also, by integrating adding to the library along with the newly introduced library requests, your app can immediately benefit from these results, giving users easy access to content they love. Even with this powerful service, you may still want to craft specific musical experiences. So this year, we're bringing playlist creation and editing to MusicKit. You can now create playlists on behalf of your users. We're also allowing items, such as songs or even whole albums, to be added to any eligible playlist in the user's library. Creating playlists are fantastic for grouping content that people love or fitting any mood your app wants to set. And by adding content to existing playlists, you allow for the various music discovery tools MusicKit offers to directly affect people. You can now also edit playlists that you've created, being able to edit the track list and metadata to make sure everything is just as you want it. And those are the ways you can interact with users' libraries from within your app. To wrap up, MusicKit received some major upgrades this year. Easily incorporate our catalog enhancements for new types, properties, and search augmentations to your existing apps for an even better experience.
Integrate library content and functionality to unlock brand-new capabilities and let users be in control of their experience.
And using MusicKit can enhance multiple different types of apps. Fitness apps, games, social media apps, mapping apps, and more can all benefit from playing or sharing music. To go even further, make sure to check out some related sessions. Dive deeper into Swift and learn about the new additions to the language to get the most out of MusicKit and other Apple Frameworks. Check out the MusicKit session from 2021 to learn how to set up your app to use the framework, initiate playback, and present subscription offers. And if you're interested in integrating with Apple Music on Android or the web, we have another session that goes over how to use Apple Music API directly.
I hope you enjoyed our session, and make sure to stay updated and engaged through our developer forums. Thank you for watching, and enjoy WWDC 2022.
-
-
4:20 - Existing catalog search request
// Loading catalog search top results var searchRequest = MusicCatalogSearchRequest( term: "Hello", types: [ Artist.self, Album.self, Song.self ] ) let searchResponse = try await searchRequest.response() print("\(searchResponse)")
-
4:44 - Loading catalog search top results
// Loading catalog search top results var searchRequest = MusicCatalogSearchRequest( term: "Hello", types: [ Artist.self, Album.self, Song.self ] ) searchRequest.includeTopResults = true let searchResponse = try await searchRequest.response() print("\(searchResponse.topResults)")
-
5:09 - Loading search suggestions
// Loading suggestions let request = MusicCatalogSearchSuggestionsRequest(term: "shaz") let response = try await request.response() print("\(response)")
-
6:30 - Loading catalog top charts
// Loading catalog top charts. let request = MusicCatalogChartsRequest( kinds: [.dailyGlobalTop, .mostPlayed, .cityTop], types: [Song.self, Playlist.self] ) let response = try await request.response() print("\(response.playlistCharts.first)")
-
8:10 - Loading audio variants
// Loading audio variants let album = … let detailedAlbum = try await album.with(.audioVariants) print("\(detailedAlbum.debugDescription)")
-
9:09 - Showing currently playing audio variants
// Showing currently playing audio variants @ObservedObject var musicPlayerQueue = ApplicationMusicPlayer.shared.queue @ObservedObject var musicPlayerState = ApplicationMusicPlayer.shared.state var body: some View { if let currentEntry = musicPlayerQueue.currentEntry { VStack { MyPlayerEntryView(currentEntry) if musicPlayerState.audioVariant == .dolbyAtmos { Image("dolby-atmos-badge") } } } }
-
10:28 - Loading recently played containers
// Loading recently played containers let request = MusicRecentlyPlayedContainerRequest() let response = try await request.response() print("\(response)")
-
10:41 - Loading recently played songs
// Loading recently played songs let request = MusicRecentlyPlayedRequest<Song>() let response = try await request.response() print("\(response)")
-
11:21 - Loading personal recommendations and printing first recommendation
// Loading personal recommendations let request = MusicPersonalRecommendationsRequest() let response = try await request.response() print("\(response.recommendations.first)")
-
11:51 - Loading personal recommendations and printing second recommendation
// Loading personal recommendations let request = MusicPersonalRecommendationsRequest() let response = try await request.response() print("\(response.recommendations[1])")
-
13:36 - Loading library playlists
@MainActor private func loadLibraryPlaylists() async throws { let request = MusicLibraryRequest<Playlist>() let response = try await request.response() self.response = response }
-
14:23 - Displaying library playlists
List { Section(header: Text("Library Playlists").fontWeight(.semibold)) { ForEach(response.items) { playlist in PlaylistCell(playlist) } } }
-
15:47 - Fetching all albums in the library
// Fetching all albums in the library let request = MusicLibraryRequest<Album>() let response = try await request.response() print("\(response)")
-
16:38 - Fetching all compilations in the library
// Fetching all compilations in the library var request = MusicLibraryRequest<Album>() request.filter(matching: \.isCompilation, equalTo: true) let response = try await request.response() print("\(response)")
-
17:08 - Fetching all dance compilations in the library
// Fetching all dance compilations in the library var request = MusicLibraryRequest<Album>() request.filter(matching: \.isCompilation, equalTo: true) request.filter(matching: \.genres, contains: danceGenre) let response = try await request.response() print("\(response)")
-
17:29 - Fetching all downloaded dance compilations in the library
// Fetching all downloaded dance compilations in the library var request = MusicLibraryRequest<Album>() request.filter(matching: \.isCompilation, equalTo: true) request.filter(matching: \.genres, contains: danceGenre) request.includeDownloadedContentOnly = true let response = try await request.response() print("\(response)")
-
18:29 - Fetching all albums sectioned by genre
// Fetching all albums sectioned by genre var request = MusicLibrarySectionedRequest<Genre, Album>() let response = try await request.response() print("\(response)")
-
19:04 - Fetching all albums sectioned by genre sorted by artist name
// Fetching all albums sectioned by genre sorted by artist name var request = MusicLibrarySectionedRequest<Genre, Album>() request.sortItems(by: \.artistName, ascending: true) let response = try await request.response() print("\(response)")
-
20:58 - Fetching relationships using the with method without a preferred source
// Fetching relationships using the with method let album = … let detailedAlbum = try await album.with(.tracks) print("\(album.tracks)")
-
21:11 - Fetching relationships using the with method and a preferred source
// Fetching relationships using the with method let album = … let detailedAlbum = try await album.with(.tracks, preferredSource: .library) print("\(album.tracks)")
-
22:09 - Adding a track to a playlist
Task { try await MusicLibrary.shared(add: selectedTrack, to: playlist) isShowingPlaylistPicker = false }
-
-
Looking for something specific? Enter a topic above and jump straight to the good stuff.
An error occurred when submitting your query. Please check your Internet connection and try again.