Exploring MusicKit and Apple Music API
Unlock the full power of MusicKit & Apple Music APIs in your apps with the best guide! Use code musickit-blog for a limited-time 35% discount!
If you’re creating an app that displays the local resources in your library, there are chances that you may want to implement a search functionality as well. In the Yours tab of my app, Musadora, I added the search functionality using the latest searchable
modifier in SwiftUI 3.0, leveraging the simplicity offered by MusicKit.
Endpoint
Apple Music API offers us the following endpoint to search the library using a query -
GET https://api.music.apple.com/v1/me/library/search
For example, I want to search for the song “Twenty Eight” by The Weeknd while writing this post. I append the required term
query to the endpoint to search for a particular term. As there’s a space between the term, I’ll have to convert the spaces into a plus sign(+) as well. The endpoint will look like this -
GET https://api.music.apple.com/v1/me/library/search?term=twenty+eight
The search can return different resources, so I’ve to be specific about the resources to include in the results. For this, we’ve required query parameter, types,
which is an array of strings. The possible values are -
- library-albums
- library-songs
- library-playlists
- library-artists
- library-music-videos
As I know I’m looking for a song, I’ll append it to the endpoint -
GET https://api.music.apple.com/v1/me/library/search?term=twenty+eight&types=library-songs
There are a few optional query parameters like limit
to return a particular number of objects or the number of objects in the specified relationship. The default value is 5, with a maximum limit of 25. There’s another, offset,
to fetch the next page or group of objects.
Now that we know the URL, it’s time to get the response!
Response
First, I created a mock function to see the data that we’re getting from the response. Based on that, I’ll create the data model. I love the new concurrency model in Swift 5.5, so I will use the async/await syntax.
private func search(term: String) async throws {
let termValue = term.replacingOccurrences(of: " ", with: "+")
var requestURLComponents = URLComponents()
requestURLComponents.scheme = "https"
requestURLComponents.host = "api.music.apple.com"
requestURLComponents.path = "/v1/me/library/search"
requestURLComponents.queryItems = [
URLQueryItem(name: "term", value: termValue),
URLQueryItem(name: "types", value: "library-songs")
]
guard let url = requestURLComponents.url else { return }
let request = MusicDataRequest(urlRequest: URLRequest(url: url))
let response = try await request.response()
print(response.debugDescription)
}
The debugDescription
variable of MusicDataResponse
gives a detailed description of the URL response and the JSON in a pretty printed format. (Thanks for the tip @Joel!)
The response we get back is LibrarySearchResponse
, which has the results property containing all the different resources. In our case, we get back a library-songs
property containing the library songs results.
Earlier, we used to create our own data model for the Song
object, but fortunately, MusicKit provides a far better way. We can model it as a MusicItemCollection<Song>
that is a collection of songs. It benefits us from using the native structures defined in MusicKit like Song
and Artwork.
The data model looks like -
struct LibrarySearchResponse: Codable {
let results: LibrarySearchResults
}
struct LibrarySearchResults: Codable {
let librarySongs: MusicItemCollection<Song>
enum CodingKeys: String, CodingKey {
case librarySongs = "library-songs"
}
}
Now, I want to search for “The Weeknd” and fetch both the library-songs and library-artists. The endpoint will be -
GET https://api.music.apple.com/v1/me/library/search?term=weeknd&types=library-songs,library-artists
If we run the code and try to decode it -
let model = try JSONDecoder().decode(LibrarySearchResponse.self, from: response.data)
print(model)
We’ll get an error as I constrained LibrarySearchResults
for songs items only. To overcome this limitation and fetch any resource type, I’ll update LibrarySearchResults
with the different library resource types.
For convenience, I created a few typealias
for better readability.
public typealias Songs = MusicItemCollection<Song>
public typealias Artists = MusicItemCollection<Artist>
public typealias Albums = MusicItemCollection<Album>
public typealias MusicVideos = MusicItemCollection<MusicVideo>
public typealias Playlists = MusicItemCollection<Playlist>
public enum MusicLibrarySearchType: String, CodingKey {
case songs = "library-songs"
case artists = "library-artists"
case albums = "library-albums"
case musicVideos = "library-music-videos"
case playlists = "library-playlists"
}
public struct MusicLibrarySearchResponse {
public let songs: Songs
public let artists: Artists
public let albums: Albums
public let musicVideos: MusicVideos
public let playlists: Playlists
}
extension MusicLibrarySearchResponse: Decodable {
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: MusicLibrarySearchType.self)
songs = try container.decodeIfPresent(Songs.self, forKey: .songs) ?? []
artists = try container.decodeIfPresent(Artists.self, forKey: .artists) ?? []
albums = try container.decodeIfPresent(Albums.self, forKey: .albums) ?? []
musicVideos = try container.decodeIfPresent(MusicVideos.self, forKey: .musicVideos) ?? []
playlists = try container.decodeIfPresent(Playlists.self, forKey: .playlists) ?? []
}
}
struct MusicLibrarySearchResponseResults: Decodable {
var results: MusicLibrarySearchResponse
}
Now, if we decode using MusicLibrarySearchResponseResults
, we’ll get the songs and artists as well!
While there’s a MusicCatalogSearchRequest
for searching the Apple Music catalog, there’s nothing for natively searching the local library. I’m working on a framework, MusadoraKit
, for bridging the gap and providing a more accessible wrapper over MusicKit. I created MusicLibrarySearchRequest
which works similar to MusicCatalogSearchRequest
-
let request = MusicLibrarySearchRequest(term: "the weeknd", types: [Artist.self, Song.self])
let response = try await request.response()
print(response.songs)
print(response.artists)
You can find the detailed documentation - MusicCatalogSearchRequest
Note: This framework is in alpha state.
Thanks for reading this post!
Exploring MusicKit and Apple Music API
Unlock the full power of MusicKit & Apple Music APIs in your apps with the best guide! Use code musickit-blog for a limited-time 35% discount!