"Unexpectedly transient" MusicPlayer Queue entries?

Hi there - I'm developing an app utilizing MusicKit and I frequently update the queue with new entries while the playlist is currently playing.

Occasionally I see this message in the console: [Playback] Inserting entries at the beginning of the queue because previous entry (MusicPlayer.Queue.Entry(id: "4B9D0B42-95E4-4F4C-B9A2-7D25F0BB4971", transientItem: Song(id: "1089596553", title: "Twin Turbo", artistName: "Sensible Soccers"))) is unexpectedly transient: [MusicPlayer.Queue.Entry(id: "21B1233B-44ED-48AE-9739-6045AF5FE170", transientItem: Song(id: "1607400995", title: "Don't Be Afraid (feat. Jungle)", artistName: "Diplo & Damian Lazarus"))]

What does this mean? What can I do to prevent it from happening?

Accepted Reply

Hello @nickfromsf,

I was able to reproduce this exact scenario with the following code:

let songsRequest = MusicCatalogResourceRequest<Song>(matching: \.id, memberOf: ["1089596553", "1607400995"])
let songsResponse = try await songsRequest.response()

let twinTurboSong = songsResponse.items[0]
let dontBeAfraidSong = songsResponse.items[1]

let queue = ApplicationMusicPlayer.shared.queue
queue.entries.insert(MusicPlayer.Queue.Entry(twinTurboSong), at: 1)
DispatchQueue.main.async {
    queue.entries.insert(MusicPlayer.Queue.Entry(dontBeAfraidSong), at: 2)
}

ApplicationMusicPlayer's Queue maintains a collection of Entries that can be mutated by the application, and that get automatically updated under the hood when the state of the playback engine's queue changes.

When this code inserts twinTurboSong, the entry being inserted is transient, indicating that it hasn't been fully inserted in the playback queue; hence, it has a temporary identifier; this entry remains transient until ApplicationMusicPlayer gets notified by the playback engine of the new state of the queue, with all inserted entries fully resolved.

This resolution process is especially important for collections such as album or playlists; you could insert a MusicPlayer.Queue.Entry with an Album; when you do, the playback engine will asynchronously resolve the tracks of the album, and the transient entry with an album will get replaced with regular entries for each track.

Back to this snippet of code, by inserting dontBeAfraidSong in the next turn of the main thread runloop, we're making it extremely likely that the playback engine hasn't had sufficient time to perform this resolution process for the previously inserted entry, the one for twinTurboSong.

I suppose you could avoid inserting new entries right after an existing Entry that has isTransient set to true.

But I don't think that's a satisfactory answer. I think you should just file a ticket on Feedback Assistant describing to the best ability what you're doing as a user of your app to get in this situation. This information will allow us to have a clear picture of the user impact of this issue.

Thank you very much in advance for your help.

Best regards,

Replies

Hello @nickfromsf,

I was able to reproduce this exact scenario with the following code:

let songsRequest = MusicCatalogResourceRequest<Song>(matching: \.id, memberOf: ["1089596553", "1607400995"])
let songsResponse = try await songsRequest.response()

let twinTurboSong = songsResponse.items[0]
let dontBeAfraidSong = songsResponse.items[1]

let queue = ApplicationMusicPlayer.shared.queue
queue.entries.insert(MusicPlayer.Queue.Entry(twinTurboSong), at: 1)
DispatchQueue.main.async {
    queue.entries.insert(MusicPlayer.Queue.Entry(dontBeAfraidSong), at: 2)
}

ApplicationMusicPlayer's Queue maintains a collection of Entries that can be mutated by the application, and that get automatically updated under the hood when the state of the playback engine's queue changes.

When this code inserts twinTurboSong, the entry being inserted is transient, indicating that it hasn't been fully inserted in the playback queue; hence, it has a temporary identifier; this entry remains transient until ApplicationMusicPlayer gets notified by the playback engine of the new state of the queue, with all inserted entries fully resolved.

This resolution process is especially important for collections such as album or playlists; you could insert a MusicPlayer.Queue.Entry with an Album; when you do, the playback engine will asynchronously resolve the tracks of the album, and the transient entry with an album will get replaced with regular entries for each track.

Back to this snippet of code, by inserting dontBeAfraidSong in the next turn of the main thread runloop, we're making it extremely likely that the playback engine hasn't had sufficient time to perform this resolution process for the previously inserted entry, the one for twinTurboSong.

I suppose you could avoid inserting new entries right after an existing Entry that has isTransient set to true.

But I don't think that's a satisfactory answer. I think you should just file a ticket on Feedback Assistant describing to the best ability what you're doing as a user of your app to get in this situation. This information will allow us to have a clear picture of the user impact of this issue.

Thank you very much in advance for your help.

Best regards,

Thanks @JoeKun, that is very helpful context. I've filed similar feedbacks before on adding items to the queue and not immediately being able to play the queue. The transient behavior you mention makes sense for these issues as well, it would be nice to have some more info in the MusicPlayer or Queue docs about what to expect when enqueuing items.

You mention the MusicPlayer getting notified that the entries are all resolved - do you have a sense of how best to tie into this notification so I can ensure I take certain actions only after all entries are resolved?

Hi @nickfromsf,

Your application can be notified of changes to the ApplicationMusicPlayer.Queue by leveraging the fact that it conforms to ObservableObject.

Our hope is that you can just use ApplicationMusicPlayer.Queue in your SwiftUI View with the @ObservedObject property wrapper.

More generally though, any time the queue changes in any way, including when transient entries are replaced with regular ones, the objectWillChange publisher will emit a new value. I suppose you could use this manually if you needed to work around some of the quirks of the current implementation of ApplicationMusicPlayer.Queue.

I hope this helps.

Best regards,