4

Fun bug in the MPMedia API

I have had an ongoing bug in my music app that I have finally tracked down now (now that I am re-writing it in swift). It has a few facets. (using systemMusicPlayer)

  1. I think I have narrowed the issues down to an MPMediaItem that has the following properties.
    • MPMediaItemPropertyIsCloudItem = true
    • assetURL = nil
    • ** these two make sense, but the following corner case (well probably pretty common) threw me for a while**
    • The first 2 items can be true, but I believe if you copied it from iTunes, it /can/ be playable (they would play most of the time for me), and there is no way to tell. I have tested this over and over and it seems to be the case, but these MPMediaItems by their existence might only sometimes cause issue, or they are fine. But you cant find out which songs these are.
  2. If you are playing a queue, and systemMusicPlayer comes across a song in your library but not downloaded or copied form iTunes, I believe it will automatically skip the song, similar to systemMusicPlayer.skipToNextItem(), but I think internally it's a different mechanism.
    • 2a. This behavior causes a basically unrecoverable problem if you are using systemMusicPlayer.skipToPreviousItem() and come across an Item that would have been skipped over - meaning, it doesn't recognize that you are trying to move back in the queue and just throws the error and moves the queue forward.
    • 2b. As far I I could tell, when the error hits going forward the MPMediaItem never becomes the nowPlayingItem. The problems going backwards get compounded by the MPMediaItem metadata (which is always available weather it is local or not) getting loaded, but the song trying to play immediately sends it forward in the queue again.

OK, so asinine and infuriating.

Now to my question:

I cant do anything about not being able to know if a cloud item is on the device or not (via iTunes). I /should/ be able to just filter out if an item has an assetURL, however, which is a guarantee that it is local and available.

let filter:MPMediaPropertyPredicate = MPMediaPropertyPredicate(value: "ipod", forProperty: MPMediaItemPropertyAssetURL, comparisonType: MPMediaPredicateComparison.Contains)

This returns 0 items. Does anyone know of a way to filter on this property? Doing it here seems like it would be the cleanest, and should leave the query returning items and itemSections. All my tables populate from the queries, and I dont think theres a way to reconstruct one manually.

The URL has a format like this: ipod-library://item/item.m4a?id=5314739480586915369

Now, I suspect it is possible to add catches when populating table views and such, but it feels really messy.

This is ios 9.2.1, Swift 2, Xcode 7.2.1

I have not yet wiped the phone and re-copied the songs. Manually downloading them from the Music app is the only way the items get an assetURL if it was not present.

solenoid
  • 954
  • 1
  • 9
  • 20

2 Answers2

3

It's not as efficient, but one thing you can do:

let query = MPMediaQuery()
let allItems = query.items ?? []
let items = allItems.filter { $0.assetURL?.scheme?.hasPrefix("ipod") ?? false }
Jimmy Dee
  • 1,070
  • 1
  • 11
  • 17
1

From MPMediaItem.h, you can see only these are filterable(commented with filterable):

MPMediaItemPropertyPersistentID
MPMediaItemPropertyMediaType
MPMediaItemPropertyTitle
MPMediaItemPropertyAlbumTitle
MPMediaItemPropertyAlbumPersistentID
MPMediaItemPropertyArtist
MPMediaItemPropertyArtistPersistentID
MPMediaItemPropertyAlbumArtist
MPMediaItemPropertyAlbumArtistPersistentID
MPMediaItemPropertyGenre
MPMediaItemPropertyGenrePersistentID
MPMediaItemPropertyCompose
MPMediaItemPropertyComposerPersistentID
MPMediaItemPropertyIsCompilation
MPMediaItemPropertyIsCloudItem
MPMediaItemPropertyHasProtectedAsset
MPMediaItemPropertyPodcastTitle
MPMediaItemPropertyPodcastPersistentID
MPMediaItemPropertyPlayCount

So it is impossible to build any query with condition on assetURL property. It is a dead end trying to do anything on assetURL unless you fetch all the MPMediaItems and do a NSArray search.

Also from somewhere in the Apple's docs, I remember vaguely, you can not by any means get the information of where a cloud item is downloaded or not.

However if you want to investigate more into cloud/local issues, I suggest you take consideration of user's music setting on if iCloud Music Library is turned on or off and looks into MPMediaItemPropertyHasProtectedAsset.

If the purpose is to detect if the song is a local song or not, you can just build a query on both isCloudItem == FALSE AND hasProtectedAsset == FALSE, in that case assetURL does not matter.

AMGuru
  • 106
  • 1
  • 3