4

I'm trying to use an AVAudioPlayerNode to play sounds from the Assets.xcassets asset catalog, but I can't figure out how to do it.

I've been using AVAudioPlayer, which can be initialized with an NSDataAsset like this:

let sound = NSDataAsset(name: "beep")!
do {
    let player = try AVAudioPlayer(data: sound.data, fileTypeHint: AVFileTypeWAVE)
    player.prepareToPlay()
    player.play()
} catch {
    print("Failed to create AVAudioPlayer")
}

I want to use an AVAudioPlayerNode instead (for pitch shifting and other reasons). I can create the engine and hook up the node OK:

var engine = AVAudioEngine()
func playSound(named name: String) {
    let mixer = engine.mainMixerNode
    let playerNode = AVAudioPlayerNode()
    engine.attach(playerNode)
    engine.connect(playerNode, to: mixer, format: mixer.outputFormat(forBus: 0))

    // play the file (this is what I don't know how to do)
}

It looks like the method to use for playing the file is playerNode.scheduleFile(). It takes an AVAudioFile, so I thought I'd try to make one. But the initializer for AVAudioFile wants a URL. As far as I can tell, assets in the asset catalog are not available by URL. I can get the data directly using NSDataAsset, but there doesn't seem to be any way to use it to populate an AVAudioFile.

Is it possible to play sounds from the asset catalog with an AVAudioPlayerNode? And if so, how?

Robert
  • 6,660
  • 5
  • 39
  • 62

1 Answers1

0

OK so your problem is that you would like to get a URL from a file in your Asset catalog right?

I've looked around but only found this answer

As it says

It basically just gets image from assets, saves its data to disk and return file URL

You should probably change it to look for MP3 files (or WAV or whatever you prefer, maybe that could be an input parameter)

So you could end up with something like:

enum SoundType: String {
    case mp3 = "mp3"
    case wav = "wav"
}

class AssetExtractor {

    static func createLocalUrl(forSoundNamed name: String, ofType type: SoundType = .mp3) -> URL? {

        let fileManager = FileManager.default
        let cacheDirectory = fileManager.urls(for: .cachesDirectory, in: .userDomainMask)[0]
        let url = cacheDirectory.appendingPathComponent("\(name).\(type)")

        guard fileManager.fileExists(atPath: url.path) else {
            guard
                let image = UIImage(named: name),
                let data = UIImagePNGRepresentation(image)
            else { return nil }

            fileManager.createFile(atPath: url.path, contents: data, attributes: nil)
            return url
        }
        return url
    }
}

Maybe a bit far fetched but I haven't found any other options.

Hope that helps you.

pbodsk
  • 6,787
  • 3
  • 21
  • 51