yaosamo
yaosamo

Reputation: 166

AVPlayer / AVAudioPlayer - Play audio fileURL from iCloud Drive (work on simulator but not iPhone) + (OSStatus error -54.)

I'm making Audio Player. Importing file from iCloud Drive using .fileImporter.

I get file URL that looks like this: file:///private/var/mobile/Library/Mobile%20Documents/com~apple~CloudDocs/_Storage/Audio-books/%D0%91%D1%80%D0%B5%D0%BD%D0%B4%D1%8F%D1%82%D0%B8%D0%BD%D0%B0/Audiobook.mp3"

Then I pass it to audio player (tried AVPlayer and AVAudioPlayer). Both works on iOS simulator. On the device after import I get error: The operation couldn’t be completed. (OSStatus error -54.)

I know it's possible, app called Evermusic does quite the same with on device files.

Thank you very much for help, any suggestions greatly appreciated, I'm seriously stuck. For future references repo of the project: https://github.com/yaosamo/AudioPlayer

Upvotes: 0

Views: 889

Answers (2)

yaosamo
yaosamo

Reputation: 166

In addition to @jnpdx answer want to add some details, and my solution example.

Few core things

✅ for my app if you need to access secured audio you need to use startAccessingSecurityScopedResource()

❌ you can't simple store URL and use it later, in fact you don't store fileURL at all. You need to use what's called bookmarkData() on your URL and store it. So later you can restore URL

Watch Apple pres here

Here's how I import file:

        .fileImporter(isPresented: $presentImporter, allowedContentTypes: [.mp3]) { result in
        switch result {
        case .success(let url):
            // Start accessing secured url
            let StartAccess = url.startAccessingSecurityScopedResource()
            defer {
                // Must stop accessing once stop using
                if StartAccess {
                    url.stopAccessingSecurityScopedResource()
                }
            }
            // Creating new book
            let newBook = Book(context: viewContext)
            let _ = print("---- Access Granted?", url.startAccessingSecurityScopedResource())
            // Getting bookmarkData of the URL
            let bookmarkData = try? url.bookmarkData()
            newBook.name = "\(url.lastPathComponent)"
            // Save bookmarkURL into CoreData
            newBook.urldata = bookmarkData
            // Specifiying parent item in CoreData
            newBook.origin = playlist.self
            try? viewContext.save()
    
        case .failure(let error):
            print(error)
        }
    }

Player retrieving URL:

func Audioplayer(bookmarkData: Data) {

// Restore security scoped bookmark
var bookmarkDataIsStale = false
let playNow = try? URL(resolvingBookmarkData: bookmarkData, bookmarkDataIsStale: &bookmarkDataIsStale)

do {
    player = try AVAudioPlayer(contentsOf: playNow!)
    // Delegate listen when audio is finished
    player?.delegate = del
    NotificationCenter.default.addObserver(forName: NSNotification.Name("ended"), object: nil, queue: .main) { (_) in
        player?.stop()
        ended = true
        let _ = print("---- Book has ended ----")
    }
} catch let error {
    print("Player Error", error.localizedDescription)
}
player?.prepareToPlay()
player?.play()
}

Thank you and again here's repo on git.

Upvotes: 1

jnpdx
jnpdx

Reputation: 52565

You need to be using startAccessingSecurityScopedResource in order to get read access to those files. See documentation:

Upvotes: 2

Related Questions