Also, I think the play method now implicitly handles prepareToPlay, so you do not need call it anymore?
Post
Replies
Boosts
Views
Activity
If you are getting the recent songs, then it is fine. I probably have been getting such warnings since the inception of the MusicKit framework and have faced no such problems because of it. Cheers!
Hi there,
I am the author of the book that you mentioned. It should be there if you are looking for code related to spatial audio (Atmos support for songs that have it). Otherwise, message me on Twitter (@rudrankriyam), and we can figure something out.
Cheers!
It is a private endpoint as of now, and I hope Apple releases it this WWDC to third-party developers.
You can use the MusicCatalogSearchRequest to search for the song for which you want the MusicItemID for and then use the MusicCatalogResourceRequest<Song>() to get more detailed data about the particular song.
In this case, it looks like Hwaji is a composer. So you can include the composer data:
https://api.music.apple.com/v1/catalog/us/songs/1544411971?include=composers
I am using:
https://api.music.apple.com/v1/catalog/us/songs/1544411971?fields[songs]=name&include=composers
Because of the limitations of 7000 characters. The response is:
{
"data": [
{
"id": "1544411971",
"type": "songs",
"href": "/v1/catalog/us/songs/1544411971",
"attributes": {
"name": "36.5 (feat. Hwaji)"
},
"relationships": {
"albums": {
"href": "/v1/catalog/us/songs/1544411971/albums",
"data": [
{
"id": "1544411968",
"type": "albums",
"href": "/v1/catalog/us/albums/1544411968"
}
]
},
"composers": {
"href": "/v1/catalog/us/songs/1544411971/composers",
"data": [
{
"id": "542118626",
"type": "artists",
"href": "/v1/catalog/us/artists/542118626",
"attributes": {
"genreNames": [
"Hip-Hop/Rap"
],
"name": "Hwaji",
"artwork": {
"width": 3995,
"height": 3995,
"url": "https://is3-ssl.mzstatic.com/image/thumb/Features124/v4/df/64/3a/df643a0f-724d-9a62-e818-310d8a1c7b25/mzl.wooenbls.jpg/{w}x{h}bb.jpg",
"bgColor": "f9859b",
"textColor1": "18030a",
"textColor2": "2f1119",
"textColor3": "451d27",
"textColor4": "572833"
},
"url": "https://music.apple.com/us/artist/hwaji/542118626"
},
"relationships": {
"albums": {
"href": "/v1/catalog/us/artists/542118626/albums",
"data": [ ]
}
}
},
{
"id": "1501799678",
"type": "artists",
"href": "/v1/catalog/us/artists/1501799678",
"attributes": {
"genreNames": [],
"name": "Buggy",
"url": "https://music.apple.com/us/artist/buggy/1501799678"
},
"relationships": {
"albums": {
"href": "/v1/catalog/us/artists/1501799678/albums",
"data": [
{
"id": "1515004653",
"type": "albums",
"href": "/v1/catalog/us/albums/1515004653"
}
]
}
}
}
]
},
"artists": {
"href": "/v1/catalog/us/songs/1544411971/artists",
"data": [
{
"id": "1297171788",
"type": "artists",
"href": "/v1/catalog/us/artists/1297171788"
}
]
}
}
}
]
}
For the "Authorization", you are supposed to add the developer token, and not the user token. You add the user token for the "Music-User-Token" header. Your code should look like:
func getStoreFrontID(userToken: String) {
let url = URL(string: "https://api.music.apple.com/v1/me/storefront")!
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.addValue("Bearer (developerToken)", forHTTPHeaderField: "Authorization")
request.addValue(userToken, forHTTPHeaderField: "Music-User-Token")
/// other code
I am not sure about the iOS version you are using, but if you are using iOS 15+, it is better to just use MusicKit for Swift. One line of code. Don't have to deal with developer nor user token.
let storefront = try await MusicDataRequest.currentCountryCode
You can set queue:
ApplicationMusicPlayer.shared.queue = [song]
or
SystemMusicPlayer.shared.queue = [song]
Then:
try await ApplicationMusicPlayer.shared.play()
or
try await SystemMusicPlayer.shared.play()
Still happening in the latest release 16.2 :/
I can relate to your queries, as I faced them too. I am currently using this:
class SomeViewModel: ObservableObject {
@Published var state = ApplicationMusicPlayer.shared.state
}
But as you mentioned, it is interesting that objectWillChange fires twice for every state update. It is more visible when you use the queue, and try to observe the changes in the currentEntry, which fires more than twice sometimes.
From my testing, I have observed that the albums usually have standard editorial notes. But for artists, the editorial notes are under the "short" editorial notes. I hope that helps!
Also, some artists may not have editorial notes, so something to keep in mind.
You can use the Apple Music API in this case. For your needs, something like fetching multiple songs by ID endpoint. Then, you can decode the response yourself, and inside it, you will find the preview URL under previews object.
Then, you can use AVPlayer to play that preview song; I guess it is for 30 seconds. Based on that, the user can buy them on iTunes.
Let me know if that works for you!
I wonder why you are not setting the queue directly instead of setting the current entry. Something like this:
player.queue = [track]
try await player.play()
Or if you want to start from a specific track from the album, you can do:
player.queue = ApplicationMusicPlayer.Queue(for: tracks, startingAt: track)
The code I used to test:
struct AlbumsView: View {
@State private var albums: MusicItemCollection<Album> = []
var body: some View {
NavigationView {
List {
ForEach(albums) { album in
NavigationLink(destination: AlbumView(album: album)) {
Text(album.title)
}
}
}
}
.task {
do {
if await MusicAuthorization.request() == .authorized {
let request = MusicLibraryRequest<Album>()
let response = try await request.response()
albums = response.items
}
} catch {
print(error)
}
}
}
}
struct AlbumView: View {
var album: Album
@State private var tracks: MusicItemCollection<Track> = []
@State private var player = ApplicationMusicPlayer.shared
var body: some View {
List {
Button("Play album") {
player.queue = [album]
}
ForEach(tracks) { track in
Button(action: {
player.queue = ApplicationMusicPlayer.Queue(for: tracks, startingAt: track)
play()
}) {
Text(track.title)
}
}
}
.task {
do {
tracks = try await album.with(.tracks).tracks ?? []
} catch {
print(error)
}
}
}
private func play() {
Task {
do {
try await player.play()
} catch {
print(error)
}
}
}
}
Also, I forgot to mention that you can use the new class MusicLibrary for iOS 16 and above to create and edit playlists. Here is an example:
let newPlaylist = MusicLibrary.shared.createPlaylist(name: "New playlist", description: "A new playlist with MusicKit", authorDisplayName: "Rudrank Riyam", items: items)
Where items are a collection of tracks as MusicItemCollection<Track>. It also returns the new playlist as Playlist.
I tried your code and realized that you could get more topResults, but you have to offset them. Increasing the limit did not work. My code:
let types: [MusicCatalogSearchable.Type] = [
Song.self,
Artist.self,
MusicVideo.self,
Album.self,
Playlist.self,
Curator.self,
RadioShow.self
]
var request = MusicCatalogSearchRequest(term: "weeknd", types: types)
request.includeTopResults = true
var topResults: MusicItemCollection<MusicCatalogSearchResponse.TopResult> = []
for offset in stride(from: 0, through: 15, by: 3) {
request.offset = offset
let response = try await request.response()
topResults += response.topResults
}
print(topResults)
The response is:
MusicItemCollection<MusicCatalogSearchResponse.TopResult>(
items: [
TopResult.artist(Artist(id: "479756766", name: "The Weeknd")),
TopResult.playlist(Playlist(id: "pl.659f6a1cac0f4232ad19d1a2cfdc9fb8", name: "The Weeknd Essentials", curatorName: "Apple Music R&B")),
TopResult.song(Song(id: "1488408568", title: "Blinding Lights", artistName: "The Weeknd")),
TopResult.song(Song(id: "1440870397", title: "I Feel It Coming (feat. Daft Punk)", artistName: "The Weeknd")),
TopResult.album(Album(id: "1603171516", title: "Dawn FM", artistName: "The Weeknd")),
TopResult.playlist(Playlist(id: "pl.774f9bceb38e45ef8f9f18e389a12160", name: "The Weeknd Video Essentials", curatorName: "Apple Music R&B")),
TopResult.song(Song(id: "1499378613", title: "Save Your Tears", artistName: "The Weeknd")),
TopResult.album(Album(id: "1440826239", title: "Beauty Behind the Madness", artistName: "The Weeknd")),
TopResult.playlist(Playlist(id: "pl.18f95e87b4ad427dba02bc7a79d0a84f", name: "The Weeknd: Influences", curatorName: "Apple Music R&B")),
TopResult.song(Song(id: "1499378615", title: "After Hours", artistName: "The Weeknd")),
TopResult.musicVideo(MusicVideo(id: "1579734725", title: "Take My Breath", artistName: "The Weeknd")),
TopResult.song(Song(id: "1563812775", title: "Save Your Tears (Remix)", artistName: "The Weeknd & Ariana Grande")),
TopResult.album(Album(id: "1499385848", title: "After Hours", artistName: "The Weeknd")),
TopResult.musicVideo(MusicVideo(id: "1528431580", title: "Smile", artistName: "Juice WRLD & The Weeknd")),
TopResult.song(Song(id: "1440870378", title: "Reminder", artistName: "The Weeknd")),
TopResult.album(Album(id: "1440858094", title: "Beauty Behind the Madness", artistName: "The Weeknd"))
]
)
However, with many network calls, this defeats the purpose of topResults, which is meant to get the results for the given search term quickly. There may be a better way to fetch all the top results, and I am all ears to know it.