I'm trying to perform a search for a song by album, artist, & title in the cases when I don't know the song's id (or if there is no isrc match)
Reading the docs, it seems that I can only search for one term at a time, but against a list of MusicCatalogSearchable Types
So my strategy would be to search for albums by name, then filter the results by artist and title.
Two questions here, really:
-
Is the above a good strategy, or is there a better way to do this?
-
For the life of my I cannot figure out from the docs, what to put in place of
[MusicCatalogSearchable.Album]
in the below.
var albumRequest = MusicCatalogSearchRequest(term: album.name, types: [MusicCatalogSearchable.Album])
Xcode doesn't like the above and gives the following error:
Type 'MusicCatalogSearchable' has no member 'Album'
Sadly, everything I've tried, results in an error.
When I look in MusicKit, I see the below, which seems empty to me:
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
public protocol MusicCatalogSearchable : MusicItem {
}
What am I missing?
Hello @Kimfucious,
Indeed, MusicCatalogSearchRequest doesn't currently have something akin to MusicCatalogResourceRequest's properties; this is partly due to the fact that you can search for more than one type at a time, so exposing the corresponding concept for search would require a more complicated API; but it seems like you have an interesting use-case for this, so feel free to file a ticket about this on Feedback Assistant.
The first couple of steps you described seem fine to me.
The third step can be simplified into a single request like this:
var detailedAlbumsRequest = MusicCatalogResourceRequest<Album>(matching: \.id, memberOf: matchedAlbums.map(\.id))
detailedAlbumsRequest.properties = [.tracks]
let detailedAlbumsResponse = try await detailedAlbumsRequest.response()
let detailedAlbums = detailedAlbumsResponse.items
Then the rest is also pretty straightforward. You can essentially do something like this:
let matchingTracks = detailedAlbums.flatMap { album -> [Track] in
let tracks = album.tracks ?? []
return tracks.filter { track in
return track.title.hasPrefix(songTitle)
}
}
I tested this briefly, and I was able to get results like these very easily:
Found: Track.song(Song(id: "697195787", title: "Harder Better Faster Stronger", artistName: "Daft Punk"))
Found: Track.song(Song(id: "703078830", title: "Harder Better Faster Stronger", artistName: "Daft Punk"))
Found: Track.song(Song(id: "696669452", title: "Harder Better Faster Stronger (The Neptunes Remix)", artistName: "Daft Punk"))
Found: Track.song(Song(id: "696669894", title: "Harder Better Faster Stronger (Jess and Crabbe Mix)", artistName: "Daft Punk"))
I understand your point about Track, but this type is important because an Album can contain a collection of items which can be either songs or music videos; the same goes for Playlist.
Converting a collection of tracks into a collection of songs is actually pretty easy though:
let matchingSongs = matchingTracks.compactMap { track -> Song? in
guard case .song(let song) = track else { return nil }
return song
}
That said, we did go the extra mile to make Track as useful as possible without having to unwrap its underlying Song or MusicVideo, by exposing directly at the Track-level all the common properties that are present in both of these underlying item types. That's how I was able to use the track's title above, without trying to get the underlying song.
Furthermore, an instance of Track is just as convenient as an instance of Song for other common tasks, such as initiating playback with MusicKit's ApplicationMusicPlayer or SystemMusicPlayer.
So if you really find it so tedious to convert a collection of tracks into a collection of songs, then I would encourage you to consider using Track in your code as the result type for your function. You might find it just as functional as Song for your purposes. As for bridging the gap for your other code-path where you find a Song by isrc (let's call it matchedSong
), converting that into a Track is as easy as doing:
let matchedTrack = Track.song(matchedSong)
I hope this helps.
Best regards,