0

I have written a download function in my app which downloads a .mp3 file from a url and saves it into Application Support directory locally.

func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
     if let url = downloadTask.originalRequest?.url {
            let destinationURL = moveFileToSupportDirectory(source: location, fileName: url.lastPathComponent , subDir: "MyApp")
            onDownloadComplete?(destinationURL, nil)
     }
}

func moveFileToSupportDirectory(source: URL, fileName: String, subDir: String) -> URL? {
    do {
        let manager = FileManager.default
        let directoryURL = try manager.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true).appendingPathComponent(subDir)
        let destination = directoryURL.appendingPathComponent(fileName)
        do {
            try FileManager.default.createDirectory (at: directoryURL, withIntermediateDirectories: true, attributes: nil)
        } catch {
            print("Error creating directory: \(error)")
            return nil
        }
        if FileManager().fileExists(atPath: destination.path) {
            print("File already exists [\(destination.path)]")
            return destination
        }
        else {
            print("Move from \(source.path) to destination \(destination.path)...")
            try manager.moveItem(at: source, to: destination)
            return destination
        }
    } catch {
        printTrace("\(error)")
        return nil
    }
}

After that when user presses "Play" button, the app will retrieve the saved URL using fileName and play it locally.

The function of retrieving URL:

func getSavedURL(of name: String) -> URL? {
    do {
        let manager = FileManager.default
        let directoryURL = try manager.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true).appendingPathComponent("MyApp")
        let destination = directoryURL.appendingPathComponent(name)
        if FileManager().fileExists(atPath: destination.path) {
            print("File exists [\(destination.path)]")
            let isReachable = try destination.checkResourceIsReachable()
            if isReachable {
                print("File is reachable \(destination.absoluteString)")
                return destination
            } else {
                print("File is unreachable")
                return nil
            }
        }
        else {
            print("File \(name) doesn't exist")
            return nil
        }
    } catch {
        print("\(error)")
        return nil
    }
}

The question is that when I download and play it in the same build, all works perfectly. However if I start another build and play the file saved previously, the audio player will throw following error:

ExtAudioFile.cpp:193:Open: about to throw 'wht?': open audio file
[avae]            AVAEInternal.h:109   [AVAudioFile.mm:134:AVAudioFileImpl: (ExtAudioFileOpenURL((CFURLRef)fileURL, &_extAudioFile)): error 2003334207

From this thread the error seems to be related to nil of audio file:
OSStatus error 2003334207 when using AVAudioPlayer

But when retrieving the URL it has already passed all the checks (i.e. exists and is reachable) and return a non-nil URL. How come the audio file cannot be played properly even though it does exist and is reachable?

Is it related to this sandbox issue?
Document directory path change when rebuild application.
But I retrieve the URL using the relative method, I think this should not happen in my case.

p.s. I use SwiftAudioPlayer to play my audio file locally. It seems to use AudioEngine inside.

Tony Chen
  • 207
  • 2
  • 13
  • How are you loading the URL when you re-run the app? As you mentioned, you shouldn't rely on the full path to stay constant, instead you should store the relative path and recreate the full path every launch. – EmilioPelaez May 24 '21 at 15:13
  • Actually I will save the file name into CoreData after downloading process for later use(e.g. xxxxx.mp3). When user wants to play the audio, I will get the file name and pass it into getSavedURL function to recreate the URL instance. – Tony Chen May 24 '21 at 15:19
  • Have you verified that the file is still accessible before playing it? You're not deleting the app between installs, right? – EmilioPelaez May 24 '21 at 15:47
  • No I didn't delete the app between two runs. Just rebuild from Xcode. May I ask how to verify if the file is still accessible? – Tony Chen May 24 '21 at 16:10
  • Execute FileManager().fileExists with the URL of where the file should be – EmilioPelaez May 24 '21 at 16:16
  • Yes I have executed this line and url.checkResourceIsReachable() as well before retrieving the URL. And they both return true. – Tony Chen May 24 '21 at 16:22
  • Please check func getSavedURL(of name: String) as in original post for more details. – Tony Chen May 24 '21 at 16:23

0 Answers0