I'm using the systemMusicPlayer to play music and want to update the playback time using addObserver forKeyPath.
[self setMusicPlayer: [MPMusicPlayerController systemMusicPlayer]];
I've tried these two methods:
[self addObserver:self forKeyPath:@"musicPlayer.currentPlaybackTime" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial context:&musicPlayer];
[self.musicPlayer addObserver:self forKeyPath:@"currentPlaybackTime" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial context:&musicPlayer];
I do get the initial values for currentPlaybackTime in:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
but I never get any calls when the player is playing the song (the whole point).
If I set the currentPlaybackTime to a specific value (locating manually using a slider), I get calls with the values I set (useless since I know what I am setting them to).
How are we supposed to track the playback time without just polling it constantly?
MusicKit
RSS for tagLet users play Apple Music and their local music library from your app using MusicKit.
Posts under MusicKit tag
133 Posts
Sort by:
Post
Replies
Boosts
Views
Activity
Since iOS 12 it has become difficult to detect the end of playback using the system music player.
In earlier iOS versions, the now playing item would be set nil and you would receive a notification that the player stopped.
In iOS 12 and later, nowPlayingItem still contains the current song and the only notification you get is MPMusicPlayerControllerPlaybackStateDidChangeNotification with the playbackState set to MPMusicPlaybackStatePaused.
Pressing pause in my car (or any remote access) generates the same conditions making it difficult to correctly detect the difference.
It would be nice if they added a notification that playback was done (similar to the other players).
Any suggestions?
How we can implement music kit in flutter app?
how we can implement music kit in flutter app?
Hey,
I've been trying to fetch my Apple Music recently played songs for an app I'm working on, and I want to access the lastPlayedDate field. If I'm not mistaken, this field should exist for a Song according to Apple's documentation:
https://developer.apple.com/documentation/musickit/song/lastplayeddate
However, whenever I try to fetch this data, the lastPlayedDate field is always nil. All the other data I'm looking for, however, seems to fetch without issue. Here's the code I'm using:
//Request as described in Apple MusicKit
//https://developer.apple.com/documentation/musickit/musicrecentlyplayedrequestable
var request = MusicRecentlyPlayedRequest<Song>()
request.limit=30
do {
let response = try await request.response()
let songs = response.items.compactMap { song -> RecentlyPlayedSong? in
let songName = song.title
let songArtist = song.artistName
let songAlbum = song.albumTitle
let artwork: MusicArtworkType
let preview_url = song.previewAssets?.first?.url?.absoluteString
if let appleMusicArtwork = song.artwork {
print("Found a song, \(song) with lastPlayedDate \(song.lastPlayedDate)")
artwork = .AppleMusic(appleMusicArtwork)
return RecentlyPlayedSong(name: songName, artist: songArtist, album: songAlbum, artwork: artwork, preview_url: preview_url, lastPlayedDate: song.lastPlayedDate ?? Date())
}
I'm trying to map the response into a custom struct I made, but here's a sample of what's getting printed to the logs:
Found a song, Song(id: "1676362342", title: "pwdr Blu (feat. Brother.)", artistName: "Kx5, deadmau5 & Kaskade") with lastPlayedDate nil
Found a song, Song(id: "881289980", title: "Worlds Apart (feat. Kerli)", artistName: "Seven Lions") with lastPlayedDate nil
Found a song, Song(id: "1501540431", title: "What’s Done Is Done", artistName: "Seven Lions & HALIENE") with lastPlayedDate nil
Even though I just listened to these songs a a few minutes ago. Anyone ever run into this issue before? Any settings I need to look at changing to get this to show?
I'm making a request to get 10 artists with their top songs at once, but for some artists it will always fail with a 504. The response is also in HTML which leads to a decoding error. This is my code
var request = MusicCatalogResourceRequest<Artist>(matching: \.id, memberOf: ids)
request.properties = properties
let response = try await request.response()
where ids is MusicItemId. Below I have an input which will always fail 100% of the time, even when retried.
10 elements
- 0 : "51639"
- 1 : "331584"
- 2 : "120199"
- 3 : "45058"
- 4 : "284786497"
- 5 : "44984"
- 6 : "37299"
- 7 : "518462"
- 8 : "39525"
- 9 : "73568"
Example response:
[DataRequesting] Failed to parse body of response with status code Unknown (504):
<!DOCTYPE html>
<html lang="en">
<head>
<style>
body {
font-family: "Helvetica Neue", "HelveticaNeue", Helvetica, Arial, sans-serif;
font-size: 15px;
font-weight: 200;
line-height: 20px;
color: #4c4c4c;
text-align: center;
}
.section {
margin-top: 50px;
}
</style>
</head>
<body>
<div class="section">
<h1></h1>
<h3>Gateway Timeout</h3>
<p>Correlation Key: WFRI6Q5HXAUJYXGNRKQ6YTBYIM</p>
</div>
</body>
</html>
I have also tried batching these into 2 requests of 5 artists instead of 1 request of 10 artists which still fails. However, I do have sets of 10 artists that work fine. Anyone know why?
I'm slowly learning the new MusicKit beta for swift.
I've learned to successfully retrieve tracks of type Song using MusicDataRequest, using the following:
...
let countryCode = try await MusicDataRequest.currentCountryCode
if let url = URL(string: "https://api.music.apple.com/v1/catalog/\(countryCode)/songs?filter[isrc]=\(isrc)") {
let dataRequest = MusicDataRequest(urlRequest: URLRequest(url: url))
let dataResponse = try await dataRequest.response()
...
However, when I decode the data, there does not seem to be any album information that I can see.
I've tried adding includes=albums to the URL, but I don't think that's the right approach, because when I veiw the Song struct in MusicKit, I don't see a reference to an Album type anywhere.
Any advice on how to retrieve the album information would be most appreciated.
Thanks.
When accessing the REST API, If you apply "include=albums" to a 'catalog//songs' endpoint requests with a filter on ISRC, the API will, without fail, return a 504 error status.
If you remove the 'include=albums' and/or replace it with something like 'include=artists' it works fine.
This has been like this for months and we need to get album details back with these requests.
Could the Apple team please respond and verify the issue as it's blocking production for us.
Thanks.
I am using MusicKit ApplicationMusicPlayer to play music in my app. Everything works fine as long as I'm not playing large playlists that contain hundreds of songs. When I to play collection of songs that is larger than around 300 I'm always getting the error message saying:
"Prepare to play failed" UserInfo={NSDebugDescription=Prepare to play failed, NSUnderlyingError=0x121d42dc0 {Error Domain=MPMusicPlayerControllerErrorDomain Code=9 "Remote call timed out" UserInfo={NSDebugDescription=Remote call timed out}}}))
It doesn't matter if songs are downloaded to the device or not.
I am aware that there is another initializer for player's queue that accepts Playlist instances but in my app users can choose to sort playlist tracks in different order than the default and that makes using that initializer not feasible for me.
I tried everything I could think of, I tried to fall back on MPMusicPlayerController and pass array of MPMusicPlayerPlayParameters to it but the result was the same.
typealias QueueEntry = ApplicationMusicPlayer.Queue.Entry
let player = ApplicationMusicPlayer.shared
let entries: [QueueEntry] = tracks
.compactMap {
guard let song = $0 as? Song else { return nil }
return QueueEntry(song)
}
Task(priority: .high) { [player] in
do {
player.queue = .init(entries, startingAt: nil)
try await player.play() // prepareToPlay failed
} catch {
print(error)
}
}
Hello. I have the following question. I call the nextBatch() method on MusicItemCollection to get the next collection of artist albums. Here is my method:
func allAlbums2(artist: Artist) async throws -> [Album] {
var allAlbums: [Album] = []
artist.albums?.forEach { allAlbums.append($0) }
guard let albums = artist.albums else { return [] }
var albumsCollection = albums
while albumsCollection.hasNextBatch {
let response = try await albumsCollection.nextBatch()
if let response {
albumsCollection = response
var albums = [Album]()
albumsCollection.forEach({ albums.append($0)})
allAlbums.append(contentsOf: albums)
}
}
return allAlbums
}
The problem is as follows. Sometimes it happens that some albums not in nextBatch are not returned (I noticed one, I didn't check the others). This happens once every 50-100 requests. The number of batches is returned the same when the album is skipped. The same albums are returned in the batch where the album was missed. I have a question, do I have a problem with multi-threading or something else or is it a problem in the API. The file contains prints of the returned batches. One with a missing album, the other without a missing one. There are about 3000 lines in the file. I will be grateful for a hint or an answer. Thank you!
Here link to file: https://files.fm/u/nj4r5wgyg3
Hello,
Problem
I am having the exact same issue as described here : https://forums.developer.apple.com/forums/thread/693310
From my understanding of the answers on this topic, it appears to be a problem on the Server Side.
Is it still the case or perhaps I am missing something ?
Here are the curl commands and their outputs :
Storefront call
➜ ~ curl -v -H 'Authorization: Bearer [VALID TOKEN]' "https://api.music.apple.com/v1/storefronts/us"
* Trying [2a02:26f0:2b00:3ab::2a1]:443...
* Connected to api.music.apple.com (2a02:26f0:2b00:3ab::2a1) port 443 (#0)
* ALPN: offers h2
* ALPN: offers http/1.1
* CAfile: /etc/ssl/cert.pem
* CApath: none
* [CONN-0-0][CF-SSL] (304) (OUT), TLS handshake, Client hello (1):
* [CONN-0-0][CF-SSL] (304) (IN), TLS handshake, Server hello (2):
* [CONN-0-0][CF-SSL] (304) (IN), TLS handshake, Unknown (8):
* [CONN-0-0][CF-SSL] (304) (IN), TLS handshake, Certificate (11):
* [CONN-0-0][CF-SSL] (304) (IN), TLS handshake, CERT verify (15):
* [CONN-0-0][CF-SSL] (304) (IN), TLS handshake, Finished (20):
* [CONN-0-0][CF-SSL] (304) (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / AEAD-CHACHA20-POLY1305-SHA256
* ALPN: server accepted h2
* Server certificate:
* subject: businessCategory=Private Organization; jurisdictionCountryName=US; jurisdictionStateOrProvinceName=California; serialNumber=C0806592; C=US; ST=California; L=Cupertino; O=Apple Inc.; CN=itunes.apple.com
* start date: Jan 23 20:23:43 2024 GMT
* expire date: Jul 21 20:33:43 2024 GMT
* subjectAltName: host "api.music.apple.com" matched cert's "api.music.apple.com"
* issuer: C=US; O=Apple Inc.; CN=Apple Public EV Server RSA CA 2 - G1
* SSL certificate verify ok.
* Using HTTP2, server supports multiplexing
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* h2h3 [:method: GET]
* h2h3 [:path: /v1/storefronts/us]
* h2h3 [:scheme: https]
* h2h3 [:authority: api.music.apple.com]
* h2h3 [user-agent: curl/7.87.0]
* h2h3 [accept: */*]
* h2h3 [authorization: Bearer [VALID TOKEN]]
* Using Stream ID: 1 (easy handle 0x12680a800)
> GET /v1/storefronts/us HTTP/2
> Host: api.music.apple.com
> user-agent: curl/7.87.0
> accept: */*
> authorization: Bearer [VALID TOKEN]
>
< HTTP/2 200
< server: daiquiri/5
< content-type: application/json;charset=utf-8
< x-apple-jingle-correlation-key: QZSH3IR75IPQYS7LSN5C5EUJRI
< x-apple-request-uuid: 86647da2-3fea-1f0c-4beb-937a2e92898a
< b3: 86647da23fea1f0c4beb937a2e92898a-1d7eb7b8ad18bc4d
< x-b3-traceid: 86647da23fea1f0c4beb937a2e92898a
< x-b3-spanid: 1d7eb7b8ad18bc4d
< apple-seq: 0.0
< apple-tk: false
< apple-originating-system: MZStorePlatform
< x-apple-application-site: MR22
< x-apple-application-instance: 3588504
< x-responding-instance: MZStorePlatform:3588504:::
< apple-timing-app: 4 ms
< access-control-allow-origin: *
< strict-transport-security: max-age=31536000; includeSubDomains
< x-daiquiri-instance: daiquiri:11896006:mr84p00it-qujn09092102:7987:24RELEASE93:daiquiri-amp-store-l7shared-int-001-mr
< x-daiquiri-instance: daiquiri:12282002:mr47p00it-qujn07081302:7987:24RELEASE93:daiquiri-amp-store-l7shared-ext-001-mr
< cache-control: public, no-transform, max-age=2625
< date: Tue, 23 Apr 2024 14:08:00 GMT
< content-length: 276
< x-cache: TCP_REFRESH_MISS from a2-17-114-29.deploy.akamaitechnologies.com (AkamaiGHost/11.4.5-55391218) (S)
< x-cache-remote: TCP_HIT from a2-17-114-18.deploy.akamaitechnologies.com (AkamaiGHost/11.4.5-55391218) (-)
< vary: Accept-Encoding
< vary: Accept-Encoding
<
* Connection #0 to host api.music.apple.com left intact
{"data":[{"id":"us","type":"storefronts","href":"/v1/storefronts/us","attributes":{"supportedLanguageTags":["en-US","es-MX","ar","ru","zh-Hans-CN","fr-FR","ko","pt-BR","vi","zh-Hant-TW"],"explicitContentPolicy":"allowed","name":"United States","defaultLanguageTag":"en-US"}}]}%
Album call
➜ ~ curl -v -H 'Authorization: Bearer [VALID TOKEN]' "https://api.music.apple.com/v1/catalog/us/albums/310730204"
* Trying [2a02:26f0:2b00:3ab::2a1]:443...
* Connected to api.music.apple.com (2a02:26f0:2b00:3ab::2a1) port 443 (#0)
* ALPN: offers h2
* ALPN: offers http/1.1
* CAfile: /etc/ssl/cert.pem
* CApath: none
* [CONN-0-0][CF-SSL] (304) (OUT), TLS handshake, Client hello (1):
* [CONN-0-0][CF-SSL] (304) (IN), TLS handshake, Server hello (2):
* [CONN-0-0][CF-SSL] (304) (IN), TLS handshake, Unknown (8):
* [CONN-0-0][CF-SSL] (304) (IN), TLS handshake, Certificate (11):
* [CONN-0-0][CF-SSL] (304) (IN), TLS handshake, CERT verify (15):
* [CONN-0-0][CF-SSL] (304) (IN), TLS handshake, Finished (20):
* [CONN-0-0][CF-SSL] (304) (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / AEAD-CHACHA20-POLY1305-SHA256
* ALPN: server accepted h2
* Server certificate:
* subject: businessCategory=Private Organization; jurisdictionCountryName=US; jurisdictionStateOrProvinceName=California; serialNumber=C0806592; C=US; ST=California; L=Cupertino; O=Apple Inc.; CN=itunes.apple.com
* start date: Jan 23 20:23:43 2024 GMT
* expire date: Jul 21 20:33:43 2024 GMT
* subjectAltName: host "api.music.apple.com" matched cert's "api.music.apple.com"
* issuer: C=US; O=Apple Inc.; CN=Apple Public EV Server RSA CA 2 - G1
* SSL certificate verify ok.
* Using HTTP2, server supports multiplexing
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* h2h3 [:method: GET]
* h2h3 [:path: /v1/catalog/us/albums/310730204]
* h2h3 [:scheme: https]
* h2h3 [:authority: api.music.apple.com]
* h2h3 [user-agent: curl/7.87.0]
* h2h3 [accept: */*]
* h2h3 [authorization: Bearer [VALID TOKEN]]
* Using Stream ID: 1 (easy handle 0x148010a00)
> GET /v1/catalog/us/albums/310730204 HTTP/2
> Host: api.music.apple.com
> user-agent: curl/7.87.0
> accept: */*
> authorization: Bearer [VALID TOKEN]
>
< HTTP/2 500
< server: daiquiri/5
< content-type: application/json; charset=utf-8
< content-length: 42
< access-control-allow-origin: *
< x-apple-jingle-correlation-key: EABGYDVEO5AFSK47FMXBMUMODY
< x-apple-application-site: st
< strict-transport-security: max-age=31536000; includeSubDomains
< x-daiquiri-instance: daiquiri:42282002:st53p00it-qujn13050102:7987:24RELEASE93:daiquiri-amp-store-l7shared-ext-001-st
< date: Tue, 23 Apr 2024 14:08:03 GMT
< x-cache: TCP_MISS from a2-17-114-29.deploy.akamaitechnologies.com (AkamaiGHost/11.4.5-55391218) (-)
<
* Connection #0 to host api.music.apple.com left intact
{"message":"An unexpected error occurred"}%
Am I missing something ? Is it a server side issue ? If so, when will it be fixed ? Otherwise, what is wrong with my approach ?
In SwiftUI there is a built-in component for displaying album artworks called Artwork but there is no equivalent for UIKit.
My current approach is to use the .url() method to read image's URL and download the image or read it from the disk but the performance is much worse than it was previously with MPMediaItem's artworkImage method.
let artworkQueue = DispatchQueue(
label: "MusicKit-ArtworkQueue",
qos: .default,
attributes: .concurrent
)
let artworkSemaphore = DispatchSemaphore(value: 5)
extension Song {
func artworkImage(for size: CGSize, completion: @escaping (UIImage?) -> Void) {
artworkQueue.async {
artworkSemaphore.wait()
defer {
artworkSemaphore.signal()
}
let imageURL = artwork?.url(
width: Int(size.width),
height: Int(size.height)
)
// I hate doing this as it might very well break in the future
guard let imageURL, imageURL.scheme == "musicKit"
else {
return completion(nil)
}
guard let imageData = try? Data(contentsOf: imageURL),
let image = UIImage(data: imageData) else {
return completion(nil)
}
completion(image)
}
}
}
I really dislike this approach because it feels hacky but somewhat works. You might ask what's the semaphore for? Well, without it I could notice that MusicKit was choking and after reading too many artworks at once.
Can someone from Apple please provide us with an example on how to use MusicKit with UIKit properly?
Ideally (IMO) we would have a method defined on Song and other MusicKit structures that returns the image for us, just like MPMediaItem had the .artwork() method. It would make our lives so much easier.
Has anyone found a way to retrieve user token if I want to make an app on Flutter that uses that to make Apple Music API calls to just retrieve the user data and display it on the application?
Something like when a user opens their app, there is a button that says connect with apple. It takes the user to give permissions for apple music and that retrieves their user token.
I know there is Music Kit on Swift but i wonder if there is something like that on flutter
We are looking at potentially adding access to Apple Music Classical into a game we have with classical music. I cannot find any documentation that states if MusicKit can access the subscription content on Apple Music Classical. We would prefer to give our players the option to play with the higher quality subscription music if desired.
Thank you in advance for your assistance.
I am trying to get the playbackStoreID via the MPMediaPickerController so I can play songs in my app. For many songs I can see and select I am getting a value of "0" for the playbackStoreID on the returned MPMediaItem.
Now if I use the "Music" app I can play those songs, but since the playbackStoreID is "0" I cannot successful get them in the queue for a MPMusicPlayerController.applicationQueuePlayer
What's the deal?
How do I get a proper ID that I can use to get those songs played?
If I cannot, how can I hide those songs from the MPMediaPickerController so a user does not think they can play those songs in my app?
I am trying to create an app for a custom Now Playing UI. How can I grab the following:
Song Name
Album Name
Artist Name
Album Art
Hi,
Is there any reasonable way to use MusicKit on the Vision Pro simulator. I have been unable to get anything working and I was wondering if the situation is the same as iOS etc where you need a physical device to test the app.
Hi, is there a way to display the animated cover art in an app? Not every album has a cover art but for the ones that have one I would like to display them instead of the artwork.
Thank you :)
I'm getting a variety of errors when I call prepareToPlay on the MPMusicPlayerController. Sometimes they happen, sometimes they don't. I'm trying to play songs from the Apple Music service. When I don't get the errors, it plays just fine. I have iOS v13.5.1 on my iPhone Xs and I'm using Xcode 11.5. This is my code:
let applicationMusicPlayer = MPMusicPlayerController.applicationMusicPlayer
applicationMusicPlayer.setQueue(with: [trackID])
applicationMusicPlayer.prepareToPlay(completionHandler:{ error in
if let error = error {
print(error.localizedDescription)
return
}
DispatchQueue.main.async{
applicationMusicPlayer.play()
}
}
These are the various errors I'm getting:
[SDKPlayback] Failed to prepareToPlay error: Error Domain=MPMusicPlayerControllerErrorDomain Code=2 "Queue was interrupted by another queue" UserInfo={NSDebugDescription=Queue was interrupted by another queue}
[SDKPlayback] Failed to prepareToPlay error: Error Domain=MPMusicPlayerControllerErrorDomain Code=9 "Preparing queue timed out" UserInfo={NSDebugDescription=Preparing queue timed out}
[SDKPlayback] Failed to prepareToPlay error: Error Domain=MPMusicPlayerControllerErrorDomain Code=6 "Failed to prepare to play" UserInfo={NSDebugDescription=Failed to prepare to play}
[SDKPlayback] applicationQueuePlayer _establishConnectionIfNeeded timeout [ping did not pong]
I need to duck the audio coming from ApplicationMusicPlayer while playing a local file using AVAudioPlayer.
I've tried using the duckOthers option as follows, but it doesn't work:
let appAudioSession = AVAudioSession.sharedInstance()
do
{
try appAudioSession.setCategory(.playAndRecord, mode: .default, options: .duckOthers)
Maybe this is because there's one session for the entire app, and ApplicationMusicPlayer is using it?
This is a fairly critical problem for my application, since Music content is always much louder than locally recorded content. Any insight appreciated.