Search code examples
iosswiftavfoundationaudio-playerbackground-music

Play IOS Music Library Content Using AVFoundation


I'm attempting to write an IOS app that will play my iPhone music library content, showing artwork, and "announcing" title, artist, etc.

I have it working nicely using Apple's Media Player framework. I can display Playlist names and queue the songs in a selected Playlist. I use the "MPMusicPlayerControllerNowPlayingItemDidChange" observer notification to pause playback, retrieve metadata, and do the announcements via AVSpeechSynthesizer. I was a happy camper until I ran into the dreaded "Media Player framwork doesn't respond to observer notifications in background" issue.

So, I started looking at the AVFoundation Framework. I found a sample that plays local song files via URLs in the background and. I'm failing miserably in attempting to retrieve Music Library content via the AVFoundation. I have also failed in supplying content retrieved via the Media Player framework to the AVFoundation player. (Note: The URLs retrieved from MPMediaItem are of a bogus "ipod-library://item/item.m4a?id=#########################" format. Creating AVPlayerItem with this "URL" doesn't work.)

Has anyone managed to accomplish this? I'm developing for my own usage. I have no intention of posting the app in Apple's App Store, so I'm willing to use hidden APIs or un-Apple approved methodology.

A Swift code example would be great. (Objective-C not so much)


Solution

  • Having fetched an MPMediaItem from the user's library, obtain its assetURL. Creating an AVPlayer from the resulting URL does work.

    Actual code from one of my example apps:

    func oneSong () -> (URL?, String?) {
        let query = MPMediaQuery.songs()
        // always need to filter out songs that aren't present
        let isPresent = MPMediaPropertyPredicate(value:false,
            forProperty:MPMediaItemPropertyIsCloudItem,
            comparisonType:.equalTo)
        query.addFilterPredicate(isPresent)
        let item = query.items?[0]
        return (item?.assetURL, item?.title)
    }
    @IBAction func doPlayOneSongAVPlayer (_ sender: Any) {
        let (url, title) = self.oneSong()
        if let url = url, let title = title {
            self.avplayer = AVPlayer(url:url)
            self.avplayer.play()
            MPNowPlayingInfoCenter.default().nowPlayingInfo = [
                MPMediaItemPropertyTitle : title
            ]
        }
    }