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.
I created a route on my nodejs server that sends a zip file of images. But unfortunately every time I make a request to the server I am able to download the zip file to but I can seem to be able to unzip file. I get the error message "unarchivable" and localized description of the error message is "The operation couldn’t be completed. (Zipper.Zipper.ArchiveError error 0.)" The nodejs route works perfectly fine with postman and my Android app.
Alamofire: 5.0.0-beta.5
swift: 4.2
I found this
Unzipping Error The operation couldn’t be completed. (Zip.ZipError error 1.) between ViewControllers
but I couldn't make sense it. I did try using a completion handler for the download process but still got the same error message
// swift code
do {
try FileManager.default.createDirectory(at: self.getDocumentsDirectory().appendingPathComponent("zipServe"), withIntermediateDirectories: true, attributes: nil)
let destination: DownloadRequest.Destination = { _, _ in
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let fileURL = documentsURL.appendingPathComponent("zipServe").appendingPathComponent("pig.zip")
return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
}
AF.download(self.main_url + "imagesAll/shop/", method: HTTPMethod.post, parameters: parameters, headers: headers, to: destination)
.downloadProgress { progress in
print("Download Progress: \(progress.isFinished)")
}
.response { response in
if response.error == nil, let imagePath = response.fileURL {
do {
print(imagePath)
if FileManager.default.fileExists(atPath: imagePath.path) {
print("File exists")
try FileManager().unzip(item: imagePath.absoluteURL, to: self.getDocumentsDirectory().appendingPathComponent("zipServe"))
} else {
print("File not found")
}
} catch let error {
print(error.localizedDescription)
}
}
}
} catch let error as NSError {
print(error.localizedDescription)
}
I should be able to see a list of the images contained within the zip file.
I have got multiple audio files on server.
I want to download all audio files first, and then when all are downloaded, i need to play them one after another.
What will be the best approach to achieve this?
Thanks!
if let audioUrl = URL(string: "http://freetone.org/ring/stan/iPhone_5-Alarm.mp3") {
// then lets create your document folder url
let documentsDirectoryURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
// lets create your destination file url
let destinationUrl = documentsDirectoryURL.appendingPathComponent(audioUrl.lastPathComponent)
print(destinationUrl)
// to check if it exists before downloading it
if FileManager.default.fileExists(atPath: destinationUrl.path) {
print("The file already exists at path")
// if the file doesn't exist
} else {
// you can use NSURLSession.sharedSession to download the data asynchronously
URLSession.shared.downloadTask(with: audioUrl, completionHandler: { (location, response, error) -> Void in
guard let location = location, error == nil else { return }
do {
// after downloading your file you need to move it to your destination url
try FileManager.default.moveItem(at: location, to: destinationUrl)
print("File moved to documents folder")
} catch let error as NSError {
print(error.localizedDescription)
}
}).resume()
}
}
I have a .GPX file contains routing info of a hiking trip which I want to load into my app. Everything is ok if I load it from remote URL (https://dl.dropboxusercontent.com/u/45741304/appsettings/Phu_si_Lung_05_01_14.gpx) but I can't load this same file from app bundle (already in "Copy bundle resources" and had correct target membership).
Here's my code for loading this file from remote URL:
var xmlParser: XMLParser!
func startParsingFileFromURL(urlString: String) {
guard let url = URL(string: urlString) else {
print("Can't load URL: \(urlString)")
return
}
self.xmlParser = XMLParser(contentsOf: url)
self.xmlParser.delegate = self
let result = self.xmlParser.parse()
print("parse from URL result: \(result)")
if result == false {
print(xmlParser.parserError?.localizedDescription)
}
}
and from the main bundle:
func startParsingFile(fileName: String, fileType: String) {
guard let urlPath = Bundle.main.path(forResource: fileName, ofType: fileType) else {
print("Can't load file \(fileName).\(fileType)")
return
}
guard let url:URL = URL(string: urlPath) else {
print("Error on create URL to read file")
return
}
self.xmlParser = XMLParser(contentsOf: url)
self.xmlParser.delegate = self
let result = self.xmlParser.parse()
print("parse from file result: \(result)")
if result == false {
print(xmlParser.parserError?.localizedDescription)
}
}
Error on load from app bundle:
parse from file result: false
Optional("The operation couldn’t be completed. (Cocoa error -1.)")
You are saying:
guard let urlPath = Bundle.main.path(forResource: fileName, ofType: fileType) else {
print("Can't load file \(fileName).\(fileType)")
return
}
guard let url:URL = URL(string: urlPath) else {
print("Error on create URL to read file")
return
}
First, it is very silly to turn a string path into a URL. You knew you wanted a URL, so why didn't you start by calling url(forResource:...)?
Second, if you ever do turn a string path into a URL, you must make a file URL.
Hi I have a whole bunch of .mp3 files I want to use with NSFileManager and store in the documents folder. Is there a way I can download the .mp3 files online and then have it save to the documents folder? This is what I'm using for a local file.
let filemanager = NSFileManager.defaultManager()
let documentsPath : AnyObject = NSSearchPathForDirectoriesInDomains(.DocumentDirectory,.UserDomainMask,true)[0]
let destinationPath:NSString = documentsPath.stringByAppendingString("/Attention.mp3")
if (!filemanager.fileExistsAtPath(destinationPath)) {
var theError: NSError?
let fileForCopy = NSBundle.mainBundle().pathForResource("Attention",ofType:"mp3")
filemanager.copyItemAtPath(fileForCopy!,toPath:destinationPath, error: &theError)
if (theError == nil) {
println("The music files has been saved.")
} else {
println("Error")
}
} else {
println("The files already exist")
}
edit/update: Xcode 11.5 • Swift 5.2
import UIKit
import AVFoundation
class ViewController: UIViewController {
var player: AVPlayer!
override func viewDidLoad() {
super.viewDidLoad()
let alarm = URL(string: "https://www.ringtonemobi.com/storage/upload/user_id_1/iphone-5-alarm-2016-08-21-01-49-25.mp3")!
do {
try alarm.download(to: .documentDirectory) { url, error in
guard let url = url else { return }
self.player = AVPlayer(url: url)
self.player.play()
}
} catch {
print(error)
}
}
}
import Foundation
extension URL {
func download(to directory: FileManager.SearchPathDirectory, using fileName: String? = nil, overwrite: Bool = false, completion: #escaping (URL?, Error?) -> Void) throws {
let directory = try FileManager.default.url(for: directory, in: .userDomainMask, appropriateFor: nil, create: true)
let destination: URL
if let fileName = fileName {
destination = directory
.appendingPathComponent(fileName)
.appendingPathExtension(self.pathExtension)
} else {
destination = directory
.appendingPathComponent(lastPathComponent)
}
if !overwrite, FileManager.default.fileExists(atPath: destination.path) {
completion(destination, nil)
return
}
URLSession.shared.downloadTask(with: self) { location, _, error in
guard let location = location else {
completion(nil, error)
return
}
do {
if overwrite, FileManager.default.fileExists(atPath: destination.path) {
try FileManager.default.removeItem(at: destination)
}
try FileManager.default.moveItem(at: location, to: destination)
completion(destination, nil)
} catch {
print(error)
}
}.resume()
}
}
Original answer
Xcode 8.3.2 • Swift 3.1
if let audioUrl = URL(string: "http://freetone.org/ring/stan/iPhone_5-Alarm.mp3") {
// create your document folder url
let documentsUrl = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
// your destination file url
let destination = documentsUrl.appendingPathComponent(audioUrl.lastPathComponent)
print(destination)
// check if it exists before downloading it
if FileManager.default.fileExists(atPath: destination.path) {
print("The file already exists at path")
} else {
// if the file doesn't exist
// just download the data from your url
URLSession.shared.downloadTask(with: audioUrl, completionHandler: { (location, response, error) in
// after downloading your data you need to save it to your destination url
guard
let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
let mimeType = response?.mimeType, mimeType.hasPrefix("audio"),
let location = location, error == nil
else { return }
do {
try FileManager.default.moveItem(at: location, to: destination)
print("file saved")
} catch {
print(error)
}
}).resume()
}
}
Xcode 10.1, Swift 4
I used the example above from #leo-dabus but broke up the code a bit into two functions. One flaw I found in that approach was that it did not handle the case where the file is already downloaded.
This example will remove any previous file that was already downloaded and write the latest version.
/// Downloads a file asynchronously
func loadFileAsync(url: URL, completion: #escaping (Bool) -> Void) {
// create your document folder url
let documentsUrl = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
// your destination file url
let destination = documentsUrl.appendingPathComponent(url.lastPathComponent)
log.info(m: "downloading file from URL: \(url.absoluteString)")
if FileManager().fileExists(atPath: destination.path) {
print("The file already exists at path, deleting and replacing with latest")
if FileManager().isDeletableFile(atPath: destination.path){
do{
try FileManager().removeItem(at: destination)
print("previous file deleted")
self.saveFile(url: url, destination: destination) { (complete) in
if complete{
completion(true)
}else{
completion(false)
}
}
}catch{
print("current file could not be deleted")
}
}
// download the data from your url
}else{
self.saveFile(url: url, destination: destination) { (complete) in
if complete{
completion(true)
}else{
completion(false)
}
}
}
}
func saveFile(url: URL, destination: URL, completion: #escaping (Bool) -> Void){
URLSession.shared.downloadTask(with: url, completionHandler: { (location, response, error) in
// after downloading your data you need to save it to your destination url
guard
let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
let location = location, error == nil
else { print("error with the url response"); completion(false); return}
do {
try FileManager.default.moveItem(at: location, to: destination)
print("new file saved")
completion(true)
} catch {
print("file could not be saved: \(error)")
completion(false)
}
}).resume()
}
I found the #leo-dabus worked straight away, but had to make two minor changes for my needs. This might be helpful for others.
Change #1: Handle filenames that come included with a path-extension
if let fileName = fileName {
if fileName.hasSuffix(self.pathExtension) {
destination = directory
.appendingPathComponent(fileName)
} else {
destination = directory
.appendingPathComponent(fileName)
.appendingPathExtension(self.pathExtension)
}
} else {
destination = directory
.appendingPathComponent(lastPathComponent)
}
Change #2: If the destination file exists, generate a unique name
E.g. generate File (2).txt to avoid overwriting File.txt, like a web browser would.
if !overwrite {
let pathExtension = destination.pathExtension
let lastComponent = destination.deletingPathExtension().lastPathComponent
var copyNumber = 2
var attemptedURL = destination
while FileManager.default.fileExists(atPath: attemptedURL.path) {
attemptedURL = destination
.deletingPathExtension()
.deletingLastPathComponent()
.appendingPathComponent("\(lastComponent) (\(copyNumber))")
.appendingPathExtension(pathExtension)
copyNumber += 1
}
destination = attemptedURL
}