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)
}
}
Post
Replies
Boosts
Views
Activity
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.
I'm building a UIKit app that reads user's Apple Music library and displays it. In MusicKit there is the Artwork structure which I need to use to display artwork images in the app. Since I'm not using SwiftUI I cannot use the ArtworkImage view that is recommended way of displaying those images but the Artwork structure has a method that returns url for the image which can be used to read the image.
The way I have it setup is really simple:
extension MusicKit.Song {
func imageURL(for cgSize: CGSize) -> URL? {
return artwork?.url(
width: Int(cgSize.width),
height: Int(cgSize.height)
)
}
func localImage(for cgSize: CGSize) -> UIImage? {
guard let url = imageURL(for: cgSize),
url.scheme == "musicKit",
let data = try? Data(contentsOf: url) else {
return nil
}
return .init(data: data)
}
}
Now, everytime I access .artwork property (so a lot of times) the main thread gets blocked and the console output gets bombared with messages like these:
2023-07-26 11:49:47.317195+0200 Plum[998:297199] [Artwork] Failed to create color analysis for artwork: <MPMediaLibraryArtwork: 0x289591590> with error; Error Domain=NSCocoaErrorDomain Code=4099 "The connection to service named com.apple.mediaartworkd.xpc was invalidated: failed at lookup with error 159 - Sandbox restriction." UserInfo={NSDebugDescription=The connection to service named com.apple.mediaartworkd.xpc was invalidated: failed at lookup with error 159 - Sandbox restriction.}
2023-07-26 11:49:47.317262+0200 Plum[998:297199] [Artwork] Failed to create color analysis for artwork: file:///var/mobile/Media/iTunes_Control/iTunes/Artwork/Originals/4b/48d7b8d349d2de858413ae4561b6ba1b294dc7
2023-07-26 11:49:47.323099+0200 Plum[998:297013] [Plum] IIOImageWriteSession:121: cannot create: '/var/mobile/Media/iTunes_Control/iTunes/Artwork/Caches/320x320/4b/48d7b8d349d2de858413ae4561b6ba1b294dc7.sb-f9c7943d-6ciLNp'error = 1 (Operation not permitted)
My guess is that the most performance-heavy task here is performing the color analysis for each artwork but IMO the property backgroundColor should not be a stored property if that's the case. I am not planning to use it anywhere and if so it should be a computed async property so it doesn't block the caller.
I know I can move the call to a background thread and that fixes the issue of blocking main thread but still the loading times for each artwork are terribly slow and that impacts the UX.
SwiftUI's ArtworkImage loads the artworks much quicker and without the errors so there must be a better way to do it.
Up until iOS 15 it was possible to set maximum number of lines of the UIButton titleLabel property, but when UIButton.Configuration is used to configure button's properties setting the titleLabel?.numberOfLines to any value is always ignored and reset after assigning text to configuration.
See the short piece of code below:
import UIKit
let button = UIButton(configuration: .plain())
button.titleLabel?.numberOfLines = 1
print("Lines: \(button.titleLabel?.numberOfLines ?? 999)") // <- prints "Lines: 1"
var buttonConfiguration = button.configuration
buttonConfiguration?.title = "Any text"
button.configuration = buttonConfiguration
print("Lines: \(button.titleLabel?.numberOfLines ?? 999)") // <- prints "Lines: 0"
Even if you set the numberOfLines to 1 it is ignored and the button renders one, or more lines of text.
I think it's a bug, there is no compiler warning about accessing the titleLabel property, as there is with imageEdgeInsets for example. If you access that one Xcode gives you a warning
'imageEdgeInsets' was deprecated in iOS 15.0: This property is ignored when using UIButtonConfiguration
If modifying titleLabel directly there should be a warning as well, but if that's the case, the UIButton.Configuration is seriously lacking some features, like setting maximum number of lines, or even a font (this can be done via AttributedString but setting font is so common that constructing AttrbitutedString every time the title is changed feels so wrong and unnecessary)
For the time being I'm forced to use the legacy button customisation
Hello, I've recently hit a roadblock when trying to integrate new Live Activities into my app. My app plays audio and the basic functionality of the Live Activity is to display some info whenever currently played track changes.
It seems that even though my app has enabled background modes for playing audio updating live activity's content just doesn't do anything. No message is printed to console, no error is thrown and the documentation suggests that it should be possible without using BackgroundTasks.
You can only start a Live Activity from your app while it’s in the foreground. However, you can update or end a Live Activity from your app while it runs in the background — for example, by using Background Tasks.
from: https://developer.apple.com/documentation/activitykit/displaying-live-data-with-live-activities
Using BackgroundTasks doesn't make sense for apps that need to display relevant information for ongoing tasks. I realize that this is probably to prevent apps from updating too often but there already is an option in Info.plist to allow frequent updates for Live Activities.
We've all wanted a way to interact with users on lock screen for years but such limitation seriously prevents a lot of apps from doing something cool with the API.
Please consider making the API less restrictive about when the activities can be updated.
So yesterday I noticed that some of images in my app stopped appearing. All of them were placed in the on-demand resources, that is I've assigned tags to them. Up until yesterday they were working correctly but since then whenever I try to load the assets I get the error message saying:
Error Domain=NSCocoaErrorDomain Code=4099 "Connection invalidated to streaming unzip service."
My code for loading the asset is pretty straightforward and it was working perfectly fine before:
let resourceRequest = NSBundleResourceRequest(tags: ["vinyl"])
defer {
resourceRequest.endAccessingResources()
}
do {
if await !resourceRequest.conditionallyBeginAccessingResources() {
try await resourceRequest.beginAccessingResources()
}
} catch {
assertionFailure("Vinyl image was not available, error: \(error)")
}
I'm obviously using it in asynchronous context, but the old method which used completion handler gives me the same error.
Has anyone encountered this issue as well? I just updated my devices to iOS 15.5, maybe that update broke on-demand resources?
Tested on Xcode 13.4, iOS 15.5