I am really struggling to figure something out and I've looked all over the internet and I can't find an answer. As you know, music apps like Spotify and Apple Music itself let the user rearrange the up next queue even while the user is listening to a song. I haven't been able to figure out how to do the same. I want to be able to completely rearrange the queue including adding, deleting, and moving around songs in the up next queue. I've tried various ways of doing so such as
musicPlayer.setQueue([queue])
but this would require me to call musicPlayer.prepareToPlay()
afterwords to update the queue which would end the current song and initiate the new queue.
I've also tried using
musicPlayer.perform(queueTransaction: { (currentQueue) in
let oldItems = currentQueue.items
var currentItem: MPMediaItem? = nil
for (i, item) in oldItems.enumerated() {
if i == musicPlayer.indexOfNowPlayingItem {
currentItem = item
continue
}
currentQueue.remove(item)
}
currentQueue.insert(queue, after: currentItem)
}, completionHandler: { (newQueue, error) in
if let error = error {
print("there was an error updating the queue", error)
return
}
print("=== NEW QUEUE ===") //insert does not work
for item in newQueue.items {
print(" -", item.title ?? "[No Title]")
}
})
but as you may know from this question: MPMusicPlayerControllerMutableQueue insert an Apple Music song not working
the insert after item function of a queue doesn't seem to work (and I've also tried setting item to nil which sometimes removed the current item and set the queue to nothing). I also know about musicPlayer.prepend([queue])
which will add the new queue after the currently playing item but I would also have to remove the previous queue from memory so the up next queue doesn't end up being over 300 songs long. I've also tried using the prepend
function inside the queue transaction but sometimes the song would stop in the middle and the music player would set the nowPlayingItem
to nil. I really hope someone knows the answer to this because I've been having this issue for over 4 days now and I can't seem to find a solution that works 100% of the time.
MPMusicPlayerController
(and its related subclasses) makes it very difficult to manage a queue like this seamlessly. If I recall correctly, you are not even able to add duplicate MPMediaItem
objects to the queue, and tapping on the playing media from Control Center will actually launch the Music app instead of your own. Essentially, your app can only act as a shell to the music playing experience, and the Music app is doing all the heavy-lifting.
Depending on your requirements, you may have another option, albeit with some compromises. The AVFoundation
framework provides
AVPlayer
and AVQueuePlayer
, which make it easier to manage a queue and provide more advanced playback options. However, you are limited to playing only downloaded, DRM-free content (i.e. songs from Apple Music will not work).
Set up your player:
let audioPlayer = AVQueuePlayer()
// Play next song in queue.
NotificationCenter.default.addObserver(self, selector: #selector(didPlayToEndTime), name: .AVPlayerItemDidPlayToEndTime, object: nil)
// Recover from failed playback.
NotificationCenter.default.addObserver(self, selector: #selector(failedToPlayToEndTime(_:)), name: .AVPlayerItemFailedToPlayToEndTime, object: nil)
// Resume music after interruptions.
NotificationCenter.default.addObserver(self, selector: #selector(handleAudioSessionInterruption(_:)), name: .AVAudioSessionInterruption, object: AVAudioSession.sharedInstance())
Insert new items in the queue:
let media = MPMediaQuery.songs()
let mediaItem: MPMediaItem = media.items.first!
// MPMediaItem objects stored in iCloud or from Apple Music
// will not have a URL, therefore, they cannot be played.
guard let assetURL: URL = mediaItem.assetURL else { return }
let playerItem = AVPlayerItem(url: assetURL)
playerItem.seek(to: kCMTimeZero)
audioPlayer.insert(playerItem, after: nil)
In my music player app, I keep an Array
of media to make really simple to reorder, insert, and remove media, and an index to keep track of the currently-playing track. I then pass the media into AVQueuePlayer
one or two at a time, so that I don't have to mess with its APIs for inserting/removing media.