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!
From the Wikipedia definition:
A music genre is a conventional category that identifies some pieces of music as belonging to a shared tradition or set of conventions. It is distinguished from musical form and musical style, although these terms are sometimes used interchangeably in practice.
Genres provide you with different categories in Apple Music that contains a collection of songs.
For example, I am currently listening to On the Train Ride Home by The Paper Kites, under the Alternative genre. In fact, most of the artists’ albums are under the Alternative genre.
Apple Music API
The Apple Music API provides us with three endpoints when writing this article. They help you to get information about the genres of the user’s music or items in the Apple Music Catalog.
- Get a Catalog Genre - Fetching a genre by using its identifier.
- Get Multiple Catalog Genres - Fetching one or more genres for a specific storefront.
- Get Catalog Top Charts Genres - Fetching all genres for the current top charts.
Genres
Before using the endpoints, let us first understand the Genres
object. It is a resource object that represents a music genre. It has four properties:
- id - The identifier for the genre.
- type - It defines the type of the object. In this case, it is always
genres
. - href - The relative location for the genre resource.
- attributes - The attributes for the genre.
Attributes
There are three attributes for each genre:
- name - The localized name of the genre
- parentId - The identifier of the parent for the genre.
- parentName - The localized name of the parent genre.
GenresResponse
It is the response that we get from hitting the above endpoints. The GenresResponse
only has one property:
- data - An array of
Genres
objects included in response to the request.
Genre in MusicKit
MusicKit for Swift contains a Genre
structure that is equivalent to the Attributes of the Genre object. It contains the following instance properties:
- id - The unique identifier for the genre. It is of the type
MusicItemID
. - name: The localized name of the genre.
- parent: The parent genre, if any. It is an optional
Genre
.
Genres Structure
While MusicKit provides you with Genre
, it is not enough to decode the response that we get from the endpoints. We need another structure that holds the array of Genres
as the data
property. So, you can use this custom structure in your code that conforms to Decodable
:
struct Genres: Decodable {
let data: [Genre]
}
Now that we know what is required let us explore the endpoints!
Get a Catalog Genre
This endpoint fetches a genre using its identifier. You provide the unique identifier and storefront as the path parameters.
https://api.music.apple.com/v1/catalog/{storefront}/genres/{id}
For example, hitting this endpoint for the Bollywood genre in Indian storefront:
https://api.music.apple.com/v1/catalog/in/genres/1263
We get the following response:
{
"data": [
{
"id": "1263",
"type": "genres",
"href": "/v1/catalog/in/genres/1263",
"attributes": {
"name": "Bollywood",
"parentId": "1262",
"parentName": "Indian"
}
}
]
}
To get the same data using MusicKit for Swift, we use the MusicDataRequest
that helps us load data from arbitrary Apple Music API endpoints like the ones mentioned above.
We get the same unstructured JSON response and decode it using JSONDecoder()
.
struct Genres: Decodable {
let data: [Genre]
}
do {
let genreID = "1263"
let countryCode = try await MusicDataRequest.currentCountryCode
let genreURL = "https://api.music.apple.com/v1/catalog/\(countryCode)/genres/\(genreID)"
guard let url = URL(string: genreURL) else {
throw URLError(.badURL)
}
let request = MusicDataRequest(urlRequest: URLRequest(url: url))
let response = try await request.response()
let genre = try JSONDecoder().decode(Genres.self, from: response.data)
print(genre)
} catch {
print(error)
}
The response in the console is:
Genres(data: [Genre(id: "1263", name: "Bollywood", parent: Genre(id: "1262", name: "Indian"))])
Get Multiple Catalog Genres
This endpoint fetches multiple genres using their identifiers. You provide the unique identifiers as query parameters and storefront as the path parameters.
https://api.music.apple.com/v1/catalog/{storefront}/genres/{id}
For example, hitting this endpoint for the Bollywood and Indian Classical genre in Indian storefront:
https://api.music.apple.com/v1/catalog/in/genres?ids=1263,1269
We get the following response:
{
"data": [
{
"id": "1263",
"type": "genres",
"href": "/v1/catalog/in/genres/1263",
"attributes": {
"name": "Bollywood",
"parentName": "Indian",
"parentId": "1262"
}
},
{
"id": "1269",
"type": "genres",
"href": "/v1/catalog/in/genres/1269",
"attributes": {
"name": "Indian Classical",
"parentName": "Indian",
"parentId": "1262"
}
}
]
}
To use it in MusicKit for Swift:
do {
let genreIDs = ["1263", "1269"]
let countryCode = try await MusicDataRequest.currentCountryCode
let genreURL = "https://api.music.apple.com/v1/catalog/\(countryCode)/genres?ids=\(genreIDs.joined(separator: ","))"
guard let url = URL(string: genreURL) else {
throw URLError(.badURL)
}
let request = MusicDataRequest(urlRequest: URLRequest(url: url))
let response = try await request.response()
let genre = try JSONDecoder().decode(Genres.self, from: response.data)
print(genre)
} catch {
print(error)
}
The response in the console is:
Genres(data: [Genre(id: "1263", name: "Bollywood", parent: Genre(id: "1262", name: "Indian")), Genre(id: "1269", name: "Indian Classical", parent: Genre(id: "1262", name: "Indian"))])
Get Catalog Top Charts Genres
This endpoint fetches all genres for the current top charts. You provide the unique identifiers as query parameters and storefront as the path parameters.
https://api.music.apple.com/v1/catalog/{storefront}/genres/
For example, hitting this endpoint with a limit of five genres in Indian storefront:
https://api.music.apple.com/v1/catalog/in/genres?limit=5
We get the following response:
{
"next": "/v1/catalog/in/genres?offset=5",
"data": [
{
"id": "34",
"type": "genres",
"href": "/v1/catalog/in/genres/34",
"attributes": {
"name": "Music"
}
},
{
"id": "20",
"type": "genres",
"href": "/v1/catalog/in/genres/20",
"attributes": {
"name": "Alternative",
"parentId": "34",
"parentName": "Music"
}
},
{
"id": "1263",
"type": "genres",
"href": "/v1/catalog/in/genres/1263",
"attributes": {
"name": "Bollywood",
"parentId": "1262",
"parentName": "Indian"
}
},
{
"id": "5",
"type": "genres",
"href": "/v1/catalog/in/genres/5",
"attributes": {
"name": "Classical",
"parentId": "34",
"parentName": "Music"
}
},
{
"id": "17",
"type": "genres",
"href": "/v1/catalog/in/genres/17",
"attributes": {
"name": "Dance",
"parentId": "34",
"parentName": "Music"
}
}
]
}
We got the next
key when adding the limit parameter. How do we access it?
MusicItemCollection
While the above method of creating a custom structure was discussed in the session Meet MusicKit for Swift, these cases of pagination are not mentioned.
After experimenting over the months, I realized that you could directly use the MusicItemCollection
for this request. Genre
conforms to MusicItem
protocol which is the requirement for MusicItemCollection
:
struct MusicItemCollection<MusicItemType> where MusicItemType : MusicItem
So, we can remove the custom structure Genres
and replace it with the following:
typealias Genres = MusicItemCollection<Genre>
All the fetch methods remain the same, but now we have access to the next batch of items if you want to paginate the genres response:
typealias Genres = MusicItemCollection<Genre>
do {
let countryCode = try await MusicDataRequest.currentCountryCode
let genreURL = "https://api.music.apple.com/v1/catalog/\(countryCode)/genres?limit=5"
guard let url = URL(string: genreURL) else {
throw URLError(.badURL)
}
let request = MusicDataRequest(urlRequest: URLRequest(url: url))
let response = try await request.response()
let genre = try JSONDecoder().decode(Genres.self, from: response.data)
print(genre)
} catch {
print(error)
}
The response in the console is:
MusicItemCollection<Genre>(
items: [
Genre(id: "34", name: "Music"),
Genre(id: "20", name: "Alternative"),
Genre(id: "1263", name: "Bollywood"),
Genre(id: "5", name: "Classical"),
Genre(id: "17", name: "Dance")
],
hasNextBatch: true
)
To fetch the next batch of collections, you can use the instance method nextBatch(limit:)
on MusicItemCollection
:
if genres.hasNextBatch {
guard let nextGenres = try await genres.nextBatch(limit: 5) else { return }
print(nextGenres)
}
The response in the console is:
MusicItemCollection<Genre>(
items: [
Genre(id: "1267", name: "Devotional & Spiritual"),
Genre(id: "7", name: "Electronic"),
Genre(id: "18", name: "Hip-Hop/Rap"),
Genre(id: "1269", name: "Indian Classical"),
Genre(id: "1185", name: "Indian Pop")
],
hasNextBatch: true
)
Getting Associated Genres
To get the associated genres for music videos, songs, albums, or artists, we take advantage of the PartialMusicProperty
.
For example, to get the genre of the song On the Train Ride Home, we use the with(_:)
instance method:
let songRequest = MusicCatalogResourceRequest<Song>(matching: \.id, equalTo: "1370781005")
let songResponse = try await songRequest.response()
guard let song = songResponse.items.first else { return }
print(song)
print(song.genres)
let detailedSong = try await song.with([.genres])
print(detailedSong)
guard let genres = detailedSong.genres else { return }
print(genres)
The response in the console is:
Song(id: "1370781005", title: "On the Train Ride Home", artistName: "The Paper Kites")
nil
Song(id: "1370781005", title: "On the Train Ride Home", artistName: "The Paper Kites")
MusicItemCollection<Genre>(
items: [
Genre(id: "20", name: "Alternative"),
Genre(id: "34", name: "Music")
]
)
Note that the genre is omitted by default, and you have to make another request to access it using the genres
property. Alternatively, you can directly get the names of the genres.
let songRequest = MusicCatalogResourceRequest<Song>(matching: \.id, equalTo: "1370781005")
let songResponse = try await songRequest.response()
guard let song = songResponse.items.first else { return }
print(song.genreNames)
The response in the console is:
["Alternative", "Music"]
Dump the Genre
If you have made it this far, there is a surprise! You only get limited information when you use the print()
.
But, if you use the dump()
method instead, you get the whole hierarchy of information. Although it isn’t much of use, we understand that more available properties are associated with a Genre
structure.
The dump is a big file, so I am redacting the details.
Console output
``` ▿ Genre(id: "1263", name: "Bollywood", parent: Genre(id: "1262", name: "Indian")) ▿ id: 1263 - rawValue: "1263" ▿ propertyProvider: MusicKit.AnyPropertyProvider ▿ wrappedPropertyProvider: MusicKit.GenrePropertyProvider - artwork: nil - editorialArtworks: nil - editorialNotes: nil - mainUberArtwork: nil ▿ name: Optional("Bollywood") - some: "Bollywood" ▿ parent: Optional(MusicKit.RelatedItemOne interesting `MusicRelationshipProperty<Genre, Playlist>` between the genre and playlist is not mentioned anywhere in the documentation. And I cannot access it either. While I am still exploring how to access it, let me know if you do! ## Conclusion This article was an exploration of using genres in MusicKit for Swift. You can use the Charts endpoint to get charts for a particular genre, which I will discuss in another article soon. Tag [@rudrankriyam](https://twitter.com/rudrankriyam) on Twitter if you have experience working with genres and want to spread some knowledge around! Thanks for reading, and I hope you're enjoying it!
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!