Search code examples
swiftxcodemacosavaudioplayeravkit

Swift AVAudioPlayer (Xcode) - Play Audio File Outside Application Bundle


In my Xcode project, I have the following code set up (simplified code):

import Cocoa
import AVKit

class ViewController: NSViewController {

    var audioPlayer = AVAudioPlayer()

    override func viewDidLoad() {
        super.viewDidLoad()

        do {
            guard let filePath = Bundle.main.path(forResource: "sample", ofType: "mp3") else {
                print("ERROR: Failed to retrieve music file Path")
                return
            }
            let fileURL = URL.init(fileURLWithPath: filePath)
            print(" PATH: \(filePath)")
            print("  URL: \(fileURL)")
            try audioPlayer = AVAudioPlayer.init(contentsOf: fileURL)
        } catch {
            print("ERROR: Failed to retrieve music file URL")
        }

        audioPlayer.play() 
    }
}

//CONSOLE PRINTS ----------:
// PATH: /Users/vakho/Library/Developer/Xcode/DerivedData/MusicPlayer-foqzsckozmcjnofvlvhuwabfssqi/Build/Products/Debug/MusicPlayer.app/Contents/Resources/sample.mp3
//  URL: file:///Users/vakho/Library/Developer/Xcode/DerivedData/MusicPlayer-foqzsckozmcjnofvlvhuwabfssqi/Build/Products/Debug/MusicPlayer.app/Contents/Resources/sample.mp3

I am successfully able to pass the filePath of sample.mp3 (contained in application bundle) to audioPlayer by converting to URL first. Calling play() function plays the audio file.

However, since I am creating a music player app, I would like to be able to play an audio file that resides in directory folders, such as desktop, downloads folder, etc.... But when I attempt to pass a filePath of audio file outside app bundle, the code breaks:

import Cocoa
import AVKit

class ViewController: NSViewController {

    var audioPlayer = AVAudioPlayer()

    override func viewDidLoad() {
        super.viewDidLoad()

        do {
            let filePathOptional: String? = "/Users/vakho/Downloads/sample.mp3"
            guard let filePath = filePathOptional else {
                print("ERROR: Failed to retrieve music file Path")
                return
            }
            let fileURL = URL.init(fileURLWithPath: filePath)
            print(" PATH: \(filePath)")
            print("  URL: \(fileURL)")
            try audioPlayer = AVAudioPlayer.init(contentsOf: fileURL)
        } catch {
            print("ERROR: Failed to retrieve music file URL")
        }

        audioPlayer.play()
    }
}

//CONSOLE PRINTS ----------:
// PATH: /Users/vakho/Downloads/sample.mp3
//  URL: file:///Users/vakho/Downloads/sample.mp3
//ERROR: Failed to retrieve music file URL

//ERRORS ----------:
//  (lldb)
//  Thread 1: EXC_BAD_ACCESS (code=1, address=0x38)

From what I could conclude, AVAudioPlayer and AVKit library is designed for accessing app assets and bundle media files, as well as streaming media from the internet. But it fails to play media from directory.

I have found several threads about the issue, all incomplete or unanswered, such as: https://forums.developer.apple.com/thread/18272

So if AVKit cannot do what I thought it should have, what library or approach can I use to access directory files? Music player apps for OSX are of course able to prompt user to scan directory and access music files. How are they doing it?


Solution

  • Seems that problem is here:

    guard let filePath = filePathOptional else {
        print("ERROR: Failed to retrieve music file Path")
        return
    }
    

    Please change the print to:

    print("ERROR: Failed to retrieve music file Path \(error.localizedDescription)")
    

    I think you'll see that the problem is that you have no access to this file. By default you have access only to sandbox of your app and Desktop folder.

    Also try to put your file to Desktop and play it.