MPMusicPlayerControllers
nowPlayingItem
no longer seems to be able to change a song. I picked up an old app of mine and was surprised to find this bug. The code use to work 1-2 years ago, but seems to be broken on iOS 16 and 17. The fact that you can only test this on a physical device makes it even harder to debug since I no longer have any iOS 15 devices. I've tried a bunch of different variations to get this to work but nothing does. The documentation seems to indicate I’m doing things correctly.
In the code sample below, a song should start playing and then after 3 seconds it should change to a different song. Instead only the first song plays. I am getting the following errors in the console when the second one is suppose to start.
Attempted to register account monitor for types client is not authorized to access: {(
"com.apple.account.iTunesStore"
)}
Failed to set now playing item error=<MPMusicPlayerControllerErrorDomain.5 "Unable to play item <MPConcreteMediaItem: 0x9e9f0ef70> 206357861099970620" {}>
The songs are on a local playlist which is downloaded to the phone used for testing. I am an Apple Music subscriber if that makes any difference. Neither of these things have change in the last few years since this code was working though.
import MediaPlayer
class Test {
let player = MPMusicPlayerController.applicationMusicPlayer
init() {
let myPlaylistsQuery = MPMediaQuery.playlists()
let playlists = myPlaylistsQuery.collections!.filter { $0.items.count > 2}
let playlist = playlists[0]
let songOne = playlists[0].items[0]
let songTwo = playlists[0].items[1]
player.setQueue(with: playlist)
play(songOne)
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
self.play(songTwo)
}
}
private func play(_ song: MPMediaItem) {
player.stop()
player.nowPlayingItem = song
player.prepareToPlay()
player.play()
}
}
I reached out to Apple via a DTS ticket and this is the fix that they provided.
From an API standpoint, there is no concept of skipping to the [nth] item in the queue. You can, however, accomplish that by simply resetting the queue, as well as pointing the
nowPlayingItem
property to the song you want playback to begin with. The queue itself can remain the same, but you need to set it again so that playback will restart with the newnowPlayingItem
. Below is an example that skips to the last item:
import MediaPlayer
class MusicPlayer {
let player = MPMusicPlayerController.applicationMusicPlayer
var playlist: MPMediaItemCollection?
func start() async {
await MPMediaLibrary.requestAuthorization()
let myPlaylistsQuery = MPMediaQuery.playlists()
playlist = myPlaylistsQuery.collections?.filter { $0.items.count > 2 }.first
if let playlist, let first = playlist.items.first {
player.setQueue(with: playlist)
play(first)
}
}
func newSong() {
if let playlist, let last = playlist.items.last {
player.setQueue(with: playlist)
play(last)
}
}
private func play(_ song: MPMediaItem) {
player.nowPlayingItem = song
player.play()
}
}