Track vs Song and Disc Numbers

I'm trying to construct what I would consider a tracklist of an Album from MusicKit for Swift, but the steps I've had to take don't feel right to me, so I feel like I am missing something. The thing is that I don't just want a bag of tracks, I want their order (trackNumber) AND what disc they're on (discNumber), if more than one disc.

At the moment, I can get to the tracks for the album with album.with([.tracks]) to get a (MusicItemCollection<Track>), but each Track DOES NOT have a discNumber. Which means, in multi disc Albums all the tracks are munged together.

To get to the discNumber's, I've had to get an equivalent Song using the same ID, which DOES contain the discNumber, as well as the trackNumber. But this collection is not connected to the Album strongly, so I've had to create a containing struct to hold both the Album (which contains Track's) and some separate Song's, making my data model slightly more complex.

(Note that you don't appear to be able to do album.with([.songs]) to skip getting to a Song via a Track.)

I can't get my head around this structure - what is the difference between a Track and a Song?

I would think that the difference is that a Track is a specific representation on a specific Album, and a Song is a more general representation that might be shared amongst more Album's (like compilations). But therefore, surely discNumber should actually be on the Track as it is specific to the Album, and arguably Song shouldn't even have trackNumber of it's own?

In any case, can anyone help with what is the quickest way to get get the trackNumber's and discNumbers' of each actual track on an Album (starting with an MusicItemID) please?

var songs = [Song]()
let detailedAlbum = try await album.with([.tracks])

if let tracks = detailedAlbum.tracks {

  for track in tracks {

    let resourceRequest = MusicCatalogResourceRequest<Song>(matching: \.id, equalTo: track.id)
    async let resourceResponse = resourceRequest.response()
    guard let song = try await resourceResponse.items.first else { break }

    songs.append(song)

  }
}

Accepted Reply

Hi Hepto,

To answer your first question about what the difference is between a Track and a Song, a Track is just an enum with an associated type which can either be a Song, or MusicVideo. So the Track is actually just a polymorphic type between Song, or MusicVideo and not it’s own entity like you were suggesting.

As for the second question as to the best way of getting the trackNumber and discNumber for every Track, the short answer is that you can’t because you can't guarantee that all Track’s have a discNumber as MusicVideos don’t have a discNumber. However, what you can do is check each track to determine if the underlying item is a Song, to get the discNumber and trackNumber like so:

let detailedAlbum = try await album.with([.tracks])
if let tracks = detailedAlbum.tracks {
     tracks.forEach { track in
          if case .song(let song) = track {
               print(song.trackNumber)
               print(song.discNumber)
          }
    }
}

  • Thanks david-apple - this makes sense now I know it! 🤦‍♂️ Maybe I should have followed the docs more closely ... it has massively simplified my code so I am very happy.

    Additionally, looking at this I realised that I can do albumRequest.properties = [.tracks] to get the tracks from the first request as I'll always need them in my use case, removing the need for the detailedAlbum. 👌

Add a Comment

Replies

Hi Hepto,

To answer your first question about what the difference is between a Track and a Song, a Track is just an enum with an associated type which can either be a Song, or MusicVideo. So the Track is actually just a polymorphic type between Song, or MusicVideo and not it’s own entity like you were suggesting.

As for the second question as to the best way of getting the trackNumber and discNumber for every Track, the short answer is that you can’t because you can't guarantee that all Track’s have a discNumber as MusicVideos don’t have a discNumber. However, what you can do is check each track to determine if the underlying item is a Song, to get the discNumber and trackNumber like so:

let detailedAlbum = try await album.with([.tracks])
if let tracks = detailedAlbum.tracks {
     tracks.forEach { track in
          if case .song(let song) = track {
               print(song.trackNumber)
               print(song.discNumber)
          }
    }
}

  • Thanks david-apple - this makes sense now I know it! 🤦‍♂️ Maybe I should have followed the docs more closely ... it has massively simplified my code so I am very happy.

    Additionally, looking at this I realised that I can do albumRequest.properties = [.tracks] to get the tracks from the first request as I'll always need them in my use case, removing the need for the detailedAlbum. 👌

Add a Comment