Exploring MusicKit: Song Structure with Code Snippets
Sponsor • Book

Exploring MusicKit and Apple Music API

The best book for learning MusicKit and adding Apple Music capabilities to your app.

The Song structure represents a single song. It conforms to the MusicItem protocol. 


struct Song

You will find this struct in almost all the other music items in one way or another, like the songs of an artist, in an album, a playlist, a station, a chart, a genre, etc. Understanding what it offers you paves the foundation for other music items.

This data model offers a variety of instance properties that you can use in your app and display. Breaking them into four chunks, you can divide them as:

  • primary properties
  • secondary properties
  • relationships
  • classical music

The order is subjective, and some properties from here may belong to a different group, but for brevity, they are categorized as such due to the huge number of properties.

Primary Properties

These are the primary properties that define information related to the particular song and a few other information related to the artist and album of the song.

  • let id: MusicItemID
    The unique identifier for the song.
  • var title: String
    The title of the song.

  • var url: URL?
    The URL for the song.

  • var trackNumber: Int?
    The song’s number in the album’s tracklist.

  • var albumTitle: String?
    The title of the album the song appears on.

  • var artistName: String
    The artist’s name.

  • var artistURL: URL?
    The artist’s URL.

  • var artwork: Artwork?
    The artwork for the song.

  • var contentRating: ContentRating?
    The rating of the content.

  • var duration: TimeInterval?
    The duration of the song.

  • var editorialNotes: EditorialNotes?
    The editorial notes for the song.

  • var playParameters: PlayParameters?
    The parameters to use to play the song.

Many of these properties can be accessed using the dot notation syntax, whereas you may need to make another call using the with(_:) discussed in MusicItem. For example, to access the song’s associated station: 


let id = MusicItemID("1444892804")
let request = MusicCatalogResourceRequest<Song>(matching: \.id, equalTo: id)
let response = try await request.response()

guard let song = response.items.first else { return }

let detailedSong = try await song.with([.station])

print(detailedSong.station)

To provide an example of a song and all the details as a sample, let’s fetch the details of the song Robbers by The 1975:

var request = MusicCatalogResourceRequest<Song>(matching: \.id, equalTo: "674118325")
request.properties = [.artistURL]
let response = try await request.response()

guard let song = response.items.first else { return }

print("id: \(song.id)")
print("title: \(song.title)")
print("url: \(String(describing: song.url))")
print("trackNumber: \(String(describing: song.trackNumber))")
print("albumTitle: \(String(describing: song.albumTitle))")
print("artistName: \(song.artistName)")
print("artistURL: \(String(describing: song.artistURL))")
print("artwork: \(String(describing: song.artwork))")
print("contentRating: \(String(describing: song.contentRating))")
print("duration: \(String(describing: song.duration))")
print("editorialNotes: \(String(describing: song.editorialNotes))")
print("playParameters: \(String(describing: song.playParameters))")

The response is:

id: 674118325
title: Robbers
url: Optional(https://music.apple.com/in/album/robbers/1442430996?i=1442431684)
trackNumber: Optional(10)
albumTitle: Optional("The 1975 (Deluxe Edition)")
artistName: The 1975
artistURL: Optional(https://music.apple.com/in/artist/the-1975/542640016)
artwork: Optional(Artwork(
  urlFormat: "https://is5-ssl.mzstatic.com/image/thumb/Music124/v4/f4/bc/71/f4bc7194-a92a-8f73-1b81-154adc503ecb/00602537497119.rgb.jpg/{w}x{h}bb.jpg",
  maximumWidth: 1500,
  maximumHeight: 1500,
  backgroundColor: <CGColor 0x280ca39c0> [<CGColorSpace 0x280ca5080> (kCGColorSpaceICCBased; kCGColorSpaceModelRGB; sRGB IEC61966-2.1; extended range)] ( 0.12549 0.12549 0.12549 1 ),
  primaryTextColor: <CGColor 0x280ca3960> [<CGColorSpace 0x280ca5080> (kCGColorSpaceICCBased; kCGColorSpaceModelRGB; sRGB IEC61966-2.1; extended range)] ( 0.898039 0.894118 0.886275 1 ),
  secondaryTextColor: <CGColor 0x280ca3a20> [<CGColorSpace 0x280ca5080> (kCGColorSpaceICCBased; kCGColorSpaceModelRGB; sRGB IEC61966-2.1; extended range)] ( 0.815686 0.807843 0.8 1 ),
  tertiaryTextColor: <CGColor 0x280ca3de0> [<CGColorSpace 0x280ca5080> (kCGColorSpaceICCBased; kCGColorSpaceModelRGB; sRGB IEC61966-2.1; extended range)] ( 0.745098 0.741176 0.733333 1 ),
  quaternaryTextColor: <CGColor 0x280ca3e40> [<CGColorSpace 0x280ca5080> (kCGColorSpaceICCBased; kCGColorSpaceModelRGB; sRGB IEC61966-2.1; extended range)] ( 0.67451 0.670588 0.662745 1 )
))
contentRating: Optional(MusicKit.ContentRating.clean)
duration: Optional(254.51)
editorialNotes: nil
playParameters: Optional(MusicKit.PlayParameters(id: 674118325, kind: "song", isLibrary: nil, catalogID: Optional(MusicKit.MusicCatalogID(value: 674118325, kind: MusicKit.MusicCatalogID.Kind.subscriptionAdamID)), libraryID: Optional(i.zpZeAOdFmZ0kMQo), deviceLocalID: Optional(MusicKit.MusicDeviceLocalID(value: 2418082271432383819, databaseID: C945B54F-03F9-489B-BB28-0AF807EFC4DE)), rawValues: [:]))

Secondary Properties

These secondary properties define other information related to the particular song that may or may not be much important for you to display or use in your app. 


  • var composerName: String?
    The name of the song’s composer.

  • var releaseDate: Date?
    The release date (or expected prerelease date) for the song.

  • var isrc: String?
    The International Standard Recording Code (ISRC) for the song.

  • var hasLyrics: Bool
    A Boolean value indicates whether the song has lyrics available in the catalog. This value has no purpose for external developers, i.e., you do not get access to the lyrics even if it is true. It is only helpful if you use Apple Music’s private API for fetching the lyrics, which, sigh, cannot be mentioned here.

  • var discNumber: Int?
    The number of the disc the song appears on.

  • var genreNames: [String]
    The names of the song’s associated genres.

  • var previewAssets: [PreviewAsset]?
    The preview assets for the song.

These properties can be accessed directly using the dot notation syntax. For example, to print the names of the song’s associated genres: 


let id = MusicItemID("1444892804")
let request = MusicCatalogResourceRequest<Song>(matching: \.id, equalTo: id)
let response = try await request.response()

guard let song = response.items.first else { return }

print(song.genreNames)

To provide an example of a song and all the details as a sample, let’s fetch the details of the song Robbers by The 1975:

var request = MusicCatalogResourceRequest<Song>(matching: \.id, equalTo: "674118325")
request.properties = [.artistURL]
let response = try await request.response()

guard let song = response.items.first else { return }

print("composerName: \(song.composerName)")
print("releaseDate: \(song.releaseDate)")
print("isrc: \(String(describing: song.isrc))")
print("hasLyrics: \(String(describing: song.hasLyrics))")
print("discNumber: \(String(describing: song.discNumber))")
print("genreNames: \(song.genreNames)")
print("previewAssets: \(String(describing: song.previewAssets))")

The response is:

composerName: Optional("George Daniel, Matthew Healy, Adam Hann & Ross MacDonald")
releaseDate: Optional(2013-09-02 00:00:00 +0000)
isrc: Optional("GBK3W1000199")
hasLyrics: true
discNumber: Optional(1)
genreNames: ["Alternative", "Music"]
previewAssets: Optional([PreviewAsset(url: "https://audio-ssl.itunes.apple.com/itunes-assets/AudioPreview115/v4/38/be/54/38be54d8-7411-fe31-e15f-c85e7d8515e8/mzaf_15200620892322734212.plus.aac.p.m4a")])

In the latest version of MusicKit released at WWDC 2022, five new instance properties were added. These are available only on iOS 16.0+, tvOS 16.0+ and watchOS 9.0+ and are unavailable on macOS and MacCatalyst. 


  • var audioVariants: [AudioVariant]?
    The variants that indicate the quality of audio available for the song.

  • var isAppleDigitalMaster: Bool?
    A Boolean value that indicates whether the song is an Apple Digital Master. Apple Digital Masters start from 24-bit files and are optimized to bring the best-sounding audio to Apple products.

  • var lastPlayedDate: Date?
    The date when the user last played the song on this device.

  • var libraryAddedDate: Date?
    The date when the user added the song to the library.

  • var playCount: Int?
    The number of times the user played the song.

To provide an example of a library song and all the details as a sample, let’s fetch the details of the song Robbers by The 1975:

var request = MusicLibraryRequest<Song>()
request.filter(matching: \.id, equalTo: "i.zpZeAOdFmZ0kMQo")
let response = try await request.response()

guard let song = response.items.first else { return }

print("audioVariants: \(String(describing: song.audioVariants))")
print("isAppleDigitalMaster: \(String(describing: song.isAppleDigitalMaster))")
print("lastPlayedDate: \(String(describing: song.lastPlayedDate))")
print("libraryAddedDate: \(String(describing: song.libraryAddedDate))")
print("playCount: \(String(describing: song.playCount))")

The response is:

audioVariants: Optional([])
isAppleDigitalMaster: Optional(true)
lastPlayedDate: Optional(2022-11-23 13:31:11 +0000)
libraryAddedDate: Optional(2016-07-09 11:10:04 +0000)
playCount: Optional(309)

Note that you cannot get the audio variants for a library song. You need to get it as an additional property for a catalog song:

var request = MusicCatalogResourceRequest<Song>(matching: \.id, equalTo: "674118325")
request.properties = [.audioVariants]
let response = try await request.response()

guard let song = response.items.first else { return }

print("audioVariants: \(String(describing: song.audioVariants))")

The response is:

audioVariants: Optional([.lossless])

Relationships

The Song has many associations that you can fetch, like the associated albums of the song or the song’s composers.


  • var albums: MusicItemCollection<Album>?
    The song’s associated albums.

  • var artists: MusicItemCollection<Artist>?
    The song’s associated artists.

  • var composers: MusicItemCollection<Artist>?
    The song’s composers.

  • var genres: MusicItemCollection<Genre>?
    The song’s associated genres.

  • var musicVideos: MusicItemCollection<MusicVideo>?
    The song’s associated music videos.
  • var station: Station?
    The song’s associated station.

To fetch all these relationships, you can again take advantage of the with(_:) method but this time, fetch all the above associations:


let id = MusicItemID("1444892804")
let request = MusicCatalogResourceRequest<Song>(matching: \.id, equalTo: id)
let response = try await request.response()

guard let song = response.items.first else { return }

let detailedSong = try await song.with([.albums, .artists, .composers, .genres, .musicVideos])

print(detailedSong)

To provide an example of a song and all the details as a sample, let’s fetch the relationships of the song Robbers by The 1975:

var request = MusicCatalogResourceRequest<Song>(matching: \.id, equalTo: "674118325")
request.properties = [.albums, .artists, .composers, .genres, .musicVideos, .station]
let response = try await request.response()

guard let song = response.items.first else { return }

print("albums: \(String(describing: song.albums?.description))")
print("artists: \(String(describing: song.artists?.description))")
print("composers: \(String(describing: song.composers))")
print("genres: \(String(describing: song.genres))")
print("musicVideos: \(String(describing: song.musicVideos))")
print("station: \(String(describing: song.station))")

The response is:

albums: Optional("MusicItemCollection<Album>(\n  items: [\n    Album(id: \"1442430996\", title: \"The 1975 (Deluxe Edition)\", artistName: \"The 1975\")\n  ]\n)")
artists: Optional("MusicItemCollection<Artist>(\n  items: [\n    Artist(id: \"542640016\", name: \"The 1975\")\n  ]\n)")
composers: Optional(MusicItemCollection<Artist>())
genres: Optional(MusicItemCollection<Genre>(
  items: [
    Genre(id: "20", name: "Alternative", parent: Genre(id: "34", name: "Music")),
    Genre(id: "34", name: "Music")
  ]
))
musicVideos: Optional(MusicItemCollection<MusicVideo>())
station: Optional(Station(
  id: "ra.674118325",
  name: "Robbers Station",
  isLive: false,
  url: "https://music.apple.com/in/station/robbers-station/ra.674118325"
))

Classical Music

  • var attribution: String?
    The name of the artist or composer to attribute to the song.

  • var movementCount: Int?
    The movement count of this song.

  • var movementName: String?
    The movement name of this song.

  • var movementNumber: Int?
    The movement number of this song.

  • var workName: String?
    The name of the associated work.

Conclusion

Working with the Song structure is something that you’ll most likely use in your app that utilizes Apple Music with MusicKit. You’ll mostly use the primary properties along with the relationships to fetch additional data about a particular song.

Thanks for reading, and share the post if you have learned something from it!

Sponsor • Book

Exploring MusicKit and Apple Music API

The best book for learning MusicKit and adding Apple Music capabilities to your app.

Written by

Rudrank Riyam

Hi, my name is Rudrank. I create apps for Apple Platforms while listening to music all day and night. Author of "Exploring MusicKit". Apple WWDC 2019 scholarship winner.