2

How can we access songs in the Apple Music library with AVAudioPlayerNode/AVAudioEngine for playback and processing?

I have asked this question in Apple forum.

Eric
  • 16,003
  • 15
  • 87
  • 139
Ivan
  • 31
  • 6

1 Answers1

5

"Apple Music" may refer to:

  • the music streaming service
  • its iOS client (known as "Music")
  • its macOS client, which doubles as a local media management and playback app (formerly known as iTunes, known as "Music" as of macOS Catalina).

Due to DRM restrictions it is not possible to play tracks from the Apple Music catalogue downloaded onto your device from the Apple Music macOS or iOS clients. However you can play audio files you own and which you've synced onto your device using the macOS Music app or the Finder app, as follows:

  1. Add the NSAppleMusicUsageDescription key to your Info.plist file, and its corresponding value
  2. Setup your AVAudioSession and AVAudioEngine
  3. Find the URL of the media item you want to play (you can use MPMediaPickerController like in the example below or you can make your own MPMediaQuery)
  4. Create an AVAudioFile from that URL
  5. Create an AVAudioPlayerNode set to play that AVAudioFile
  6. Connect the player node to the engine's output node
import UIKit
import AVFoundation
import MediaPlayer

class ViewController: UIViewController {

    let engine = AVAudioEngine()

    override func viewDidLoad() {
        super.viewDidLoad()

        let mediaPicker = MPMediaPickerController(mediaTypes: .music)
        mediaPicker.allowsPickingMultipleItems = false
        mediaPicker.showsItemsWithProtectedAssets = false // These items usually cannot be played back
        mediaPicker.showsCloudItems = false // MPMediaItems stored in the cloud don't have an assetURL
        mediaPicker.delegate = self
        mediaPicker.prompt = "Pick a track"
        present(mediaPicker, animated: true, completion: nil)
    }

    func startEngine(playFileAt: URL) {
        do {
            try AVAudioSession.sharedInstance().setCategory(.playback)

            let avAudioFile = try AVAudioFile(forReading: playFileAt)
            let player = AVAudioPlayerNode()

            engine.attach(player)
            engine.connect(player, to: engine.mainMixerNode, format: avAudioFile.processingFormat)

            try engine.start()
            player.scheduleFile(avAudioFile, at: nil, completionHandler: nil)
            player.play()
        } catch {
            assertionFailure(String(describing: error))
        }
    }
}

extension ViewController: MPMediaPickerControllerDelegate {
    func mediaPicker(_ mediaPicker: MPMediaPickerController, didPickMediaItems mediaItemCollection: MPMediaItemCollection) {
        guard let item = mediaItemCollection.items.first else {
            print("no item")
            return
        }
        print("picking \(item.title!)")
        guard let url = item.assetURL else {
            return print("no url")
        }

        dismiss(animated: true) { [weak self] in
            self?.startEngine(playFileAt: url)
        }
    }

    func mediaPickerDidCancel(_ mediaPicker: MPMediaPickerController) {
        dismiss(animated: true, completion: nil)
    }
}
Eric
  • 16,003
  • 15
  • 87
  • 139
  • 1
    Note for anyone not getting a back the url from certain tracks make sure to set mediaPicker.showsItemsWithProtectedAssets = false so that the picker omits DRM protected content, that you can not load into AVAudioPlayerNode. – Sam Bing Aug 27 '20 at 12:44
  • 1
    Cheers @SamBing. I've updated the code accordingly. – Eric Sep 01 '20 at 13:34