swift can not save .m3u8 file to gallery - swift

Im using this below method to download and save my video to gallery, with .mp4 it's work normally, but when change to .m3u8 it's always fail.
func downloadVideoLinkAndCreateAsset(_ videoLink: String,_ fileName : String) {
// use guard to make sure you have a valid url
guard let videoURL = URL(string: videoLink) else { return }
guard let documentsDirectoryURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
let fileNameToSave = "CiviX_HistoryVideo_\(fileName)"
// check if the file already exist at the destination folder if you don't want to download it twice
if !FileManager.default.fileExists(atPath: documentsDirectoryURL.appendingPathComponent(fileNameToSave).path) {
// set up your download task
URLSession.shared.downloadTask(with: videoURL) { (location, response, error) -> Void in
// use guard to unwrap your optional url
guard let location = location else { return }
// create a deatination url with the server response suggested file name
let destinationURL = documentsDirectoryURL.appendingPathComponent(fileNameToSave)
print("destinationURL: \(destinationURL)")
do {
try FileManager.default.moveItem(at: location, to: destinationURL)
PHPhotoLibrary.requestAuthorization({ (authorizationStatus: PHAuthorizationStatus) -> Void in
// check if user authorized access photos for your app
if authorizationStatus == .authorized {
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: destinationURL)}) { completed, error in
if completed {
print("Video asset created")
} else {
print("Video asset create failed: \(error?.localizedDescription)")
}
}
}
})
} catch { print("file manager error: \(error.localizedDescription)") }
}.resume()
} else {
print("File already exists at destination url")
}
}
then here is method to call
let urlString = response.replacingOccurrences(of: "\"", with: "") -> my m3u8 URL
let videoImageUrl = "https://www.sample-videos.com/video123/mp4/720/big_buck_bunny_720p_1mb.mp4" -> always success
//TEST MP4 file -> ALWAYS SUCCESS
self.downloadVideoLinkAndCreateAsset(videoImageUrl, "big_buck_bunny_720p_1mb.mp4")
//TEST M3U8 FIlE -> FAIL
self.downloadVideoLinkAndCreateAsset(urlString, history.fileName!) -> fileName format is 'abc.mp4'
The log result for MP4
destinationURL: file:///Users/thehe/Library/Developer/CoreSimulator/Devices/05C6DE76-6609-4E4A-B00D-2CE3622D2EF8/data/Containers/Data/Application/90994674-6C07-47F9-A880-D1A80CDA0C27/Documents/CiviX_HistoryVideo_big_buck_bunny_720p_1mb.mp4
-> Video asset created
THe log result for M3U8
self.downloadVideoLinkAndCreateAsset(urlString, history.fileName!)
destinationURL: file:///Users/thehe/Library/Developer/CoreSimulator/Devices/05C6DE76-6609-4E4A-B00D-2CE3622D2EF8/data/Containers/Data/Application/DA6ABC38-4E0A-44C7-9C56-8B65F1DC0D4D/Documents/CiviX_HistoryVideo_20-1-2019_3h18m32s.mp4
-> Video asset create failed: Optional("The operation couldn’t be completed. (Cocoa error -1.)")
I also tried to save with .m3u8 extension but still not working
self.downloadVideoLinkAndCreateAsset(urlString, "TEST_M3U8_FILE.m3u8")
destinationURL: file:///Users/thehe/Library/Developer/CoreSimulator/Devices/05C6DE76-6609-4E4A-B00D-2CE3622D2EF8/data/Containers/Data/Application/9B42A55B-4E3E-4A20-A0DC-6E1ED22471A2/Documents/CiviX_HistoryVideo_TEST_M3U8_FILE.m3u8
-> Video asset create failed: Optional("The operation couldn’t be completed. (Cocoa error -1.)")

M3U8 is an audio/video playlist file and it cannot be saved to the gallery.

Related

Audio URL cannot be played even though the file exists and is reachable

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.

Unable to unzip a downloaded file

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.

How to first download audio files from URLs and then play all

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()
}
}

Load XML file from main Bundle in Swift 3.0

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.

Download file from server using Swift

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
}