Problem with applicationQueuePlayer Perform Method & Iterating Over Items

When using the applicationQueuePlayer perform(queueTransaction:completionHandler:) method, it takes multiple seconds to iterate over the items of either the MPMusicPlayerControllerMutableQueue or the MPMusicPlayerControllerQueue. While the iteration happens, the app is frozen (as this is done on the main thread) and I get the following error in console:

ASYNC-WATCHDOG-1: Attempting to wake up the remote process

I am dealing with a queue of a few hundred, but I don't believe the issue is with the number of MPMediaItems as when I get a similar number of items from e.g. MPMediaPlaylist, iterations happen instantly.

Here is a simplified version of my code:

var musicPlayerController = MPMusicPlayerController.applicationQueuePlayer

self.musicPlayerController.perform { (currentQueue) in
    return
} completionHandler: { (newQueue, error) in
    if let e = error {
        print(e)
    } else {
        let items = newQueue.items
        print("Starting iteration")
        let tracks = items.map { Track(item: $0) }
        print("Ending iteration")
    }
}

In the console, I'll see "Starting iteration" printed in the console straight away as the method is called. And then I'll see "Ending iteration" multiple seconds later, during which the app is frozen.

This method is called anytime the .MPMusicPlayerControllerQueueDidChange notification is posted.

Answers

Hi @jackvanderpump,

I've tested with pretty large playlists, also put it in MPMusicPlayerControllerQueueDidChange handler as you did and have not ran into the problem in my configuration.

Maybe this is specific to some content, as you also observed. Would you be able to share the queue items with us?

Please use https://feedbackassistant.apple.com/ if needed.

Thanks you!

Hi @MinhMP, thanks for your help. I don't think it is due to any issue with the items (I've tried with different devices, with different apple IDs, with different collections) and I still have this issue. If you have e.g. 200 tracks in the queue, it perhaps is only a second to iterate through the items – so easy to dismiss as not a problem – but this is at least 10x longer than it would normally take to iterate through the same amount of items e.g. the first 200 tracks in library.

This little app should demonstrate the issue (just add NSAppleMusicUsageDescription to info.plist, and restart app after giving permission, you also need to have 200 tracks in your library).

The question is why does it take so much longer to iterate through items from the queue than from anywhere else?

import SwiftUI

import MediaPlayer



struct ContentView: View {

    

    @StateObject var musicManager = MusicManager()

    

    var body: some View {

        VStack(spacing: 50) {

            Spacer()

            Label("Ready To Test?", systemImage: musicManager.readyToTest ? "checkmark" : "xmark")

            Text("You need 200 tracks in library. You have \(musicManager.libraryTrackCount) tracks")

                .fixedSize(horizontal: false, vertical: true)

            Spacer()

            Button(action: {

                musicManager.getTracks()

            }) {

                Text("Get first 200 tracks in library")

            }

            Button(action: {

                musicManager.getQueue()

            }) {

                Text("Get tracks in queue")

            }

            Spacer()

            Text("Time to get tracks (\(musicManager.trackCount) items): \(musicManager.trackTimer)")

            Text("Time to get queue (\(musicManager.queueCount) items): \(musicManager.queueTimer)")

            Spacer()

        }

    }

}



class MusicManager: ObservableObject {

    

    var applicationQueuePlayer = MPMusicPlayerController.applicationQueuePlayer

    let libraryTracks = MPMediaQuery.songs().items

    

    @Published var libraryTrackCount = Int()

    @Published var queueTimer = String()

    @Published var queueCount = Int()

    @Published var trackTimer = String()

    @Published var trackCount = Int()

    @Published var readyToTest = false

    

    init() {

        play200Tracks()

        libraryTrackCount = libraryTracks?.count ?? 0

        if libraryTracks?.count ?? 0 > 200 { readyToTest = true }

    }

    

    func play200Tracks() {

        if let tracks = libraryTracks {

            let tracks200 = Array(tracks.prefix(200))

            let collection = MPMediaItemCollection(items: tracks200)

            applicationQueuePlayer.setQueue(with: collection)

            applicationQueuePlayer.play()

        }

    }

    

    func getTracks() {

        if let allTracks = libraryTracks {

            let tracks = Array(allTracks.prefix(200))

            let start = CFAbsoluteTimeGetCurrent()

            for track in tracks {

                print(track.title ?? "")

            }

            trackTimer = String(CFAbsoluteTimeGetCurrent() - start)

            trackCount = tracks.count

            print("Track Timer \(trackTimer) for \(trackCount) items")

        }

    }

    

    func getQueue() {

        applicationQueuePlayer.perform { queue in

            return

        } completionHandler: { queue, error in

            let tracks = queue.items

            let start = CFAbsoluteTimeGetCurrent()

            for track in tracks {

                print(track.title ?? "")

            }

            self.queueTimer = String(CFAbsoluteTimeGetCurrent() - start)

            self.queueCount = tracks.count

            print("Queue Timer \(self.queueTimer) for \(self.queueCount) items")

        }

    }

    

}

I still haven't found a solution to this. Does anyone else have the same issue when using the code/app posted above?

Hi @jackvanderpump,

We are currently working on refining the new public API for playback in the new MusicKit framework. What we're working on might solve your performance issues.

Stay tuned.

Best regards,

Hello @jackvanderpump,

We have made a number of changes to MusicKit's playback API in iOS 15 beta 4.

You can now access ApplicationMusicPlayer.shared.queue to monitor the queue. This object is observable, so you can even use it with @ObservedObject in your SwiftUI views.

Furthermore the underlying implementation of this new API is designed to have better performance characteristics than the older MediaPlayer APIs.

I hope this helps.

Best regards,