How to download mp3 file from MPMediaPickerController using swift language? - ios

My application is download song from MPMediaPickerController and save mp3 file in Document directory
File is download successfully in .m4a formate, But i need mp3 file. i'm already change file extension .m4a to .mp3 but this file is not playing.
I have tried a lot of near by examples but didn't succeed to solve this issue.
How can I do it?
func mediaPicker(_ mediaPicker: MPMediaPickerController, didPickMediaItems mediaItemCollection: MPMediaItemCollection) {
mediaPicker.dismiss(animated: true) {
let item: MPMediaItem = mediaItemCollection.items[0]
let pathURL: URL? = item.value(forProperty: MPMediaItemPropertyAssetURL) as? URL
if pathURL == nil {
self.alert(message: "Sorry, the returned mediaItemCollection appeart to be empty")
return
}
let song_Title = item.value(forProperty: MPMediaItemPropertyTitle) as! String
let filename_song = song_Title.replacingOccurrences(of: " ", with: "_")
// get file extension andmime type
let str = pathURL!.absoluteString
let str2 = str.replacingOccurrences( of : "ipod-library://item/item", with: "")
let arr = str2.components(separatedBy: "?")
var mimeType = arr[0]
mimeType = mimeType.replacingOccurrences( of : ".", with: "")
// Export the ipod library as .m4a file to local directory for remote upload
let exportSession = AVAssetExportSession(asset: AVAsset(url: pathURL!), presetName: AVAssetExportPresetAppleM4A)
exportSession?.shouldOptimizeForNetworkUse = true
exportSession?.outputFileType = AVFileType.m4a
exportSession?.metadata = AVAsset(url: pathURL!).metadata
let documentURL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
let outputURL = documentURL.appendingPathComponent("\(filename_song).m4a")
//Delete Existing file
do {
try FileManager.default.removeItem(at: outputURL)
} catch let error as NSError {
print(error.debugDescription)
}
exportSession?.outputURL = outputURL
exportSession?.exportAsynchronously(completionHandler: { () -> Void in
if (exportSession!.status == AVAssetExportSession.Status.completed)
{
print("AV export succeeded.")
self.alert(message: "File download successfully")
do {
let newURL = outputURL.deletingPathExtension().appendingPathExtension("mp3")
let str = try FileManager.default.moveItem(at: outputURL, to: newURL)
print(str)
} catch {
print("The file could not be loaded")
}
}
else if (exportSession!.status == AVAssetExportSession.Status.cancelled)
{
print("AV export cancelled.")
}
else
{
print("AV export failed with error:- ", exportSession!.error!.localizedDescription)
}
})
}
}

Related

Saving video in locally (directory) in Swift?

I try to save given video locally after then I need those saved videos for playing video in my app. I can't handle the saving video. Here is my saving try :
func saveVideoDocumentDirectory(url : URL){
let fileManager = FileManager.default
let paths = (NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString).appendingPathComponent(".MOV")
do{
let videoData = try Data(contentsOf: url)
fileManager.createFile(atPath: paths as String, contents: videoData, attributes: nil)
}catch{
//
}
}
here is the get file try
func getVideo(){
let fileManager = FileManager.default
let videoPAth = (self.getDirectoryPath() as NSString).appendingPathComponent(".MOV")
if fileManager.fileExists(atPath: videoPAth){
print(videoPAth)
play(url: URL(string: videoPAth)!)
}else{
print("No Video")
}
}
here is my play video func :
func play(url : URL)
{
let player = AVPlayer(url: url)
let playerViewController = AVPlayerViewController()
playerViewController.player = player
present(playerViewController, animated: true)
{
playerViewController.player!.play()
}
}
Instead of Filemanager.createFile(), try using write instead.
let videoData = try Data(contentsOf: url)
try videoData.write(to: paths, options: .atomic)
Also, I recommend creating a folder first (from this answer).
extension URL {
static func createFolder(folderName: String) -> URL? {
let fileManager = FileManager.default
// Get document directory for device, this should succeed
if let documentDirectory = fileManager.urls(for: .documentDirectory,
in: .userDomainMask).first {
// Construct a URL with desired folder name
let folderURL = documentDirectory.appendingPathComponent(folderName)
// If folder URL does not exist, create it
if !fileManager.fileExists(atPath: folderURL.path) {
do {
// Attempt to create folder
try fileManager.createDirectory(atPath: folderURL.path,
withIntermediateDirectories: true,
attributes: nil)
} catch {
// Creation failed. Print error & return nil
print(error.localizedDescription)
return nil
}
}
// Folder either exists, or was created. Return URL
return folderURL
}
// Will only be called if document directory not found
return nil
}
}
Then, you can save like this:
guard let folderURL = URL.createFolder(folderName: "StoredVideos") else {
print("Can't create url")
return
}
let permanentFileURL = folderURL.appendingPathComponent(nameOfYourFile).appendingPathExtension("MOV")
let videoData = try Data(contentsOf: url)
try videoData.write(to: permanentFileURL, options: .atomic)
This will save you the hassle of NSSearchPathForDirectoriesInDomains.

How to get the url for a video saved in temporary directory

I need to save a video file to a temp directory and save the reference to it URL. Currently I have tried using fileManager to create a temp directory and then createFile(atPath: tempDirString, contents: vidData, attributes: nil). But I dont/am not able to save the full reference to the file.
What I have tried:
PHCachingImageManager().requestAVAsset(forVideo: (cell?.assetPH)!, options: nil) { (avAsset, _, _) in
if let avAsset = avAsset {
print(avAsset, " the avasset?")
// vidAVAsset = avAsset
let avPlayerItem = AVPlayerItem(asset: avAsset)
let avPlayer = AVPlayer(playerItem: avPlayerItem)
print(avPlayer, "<-- good?")
let finalURL = self.urlOfCurrentlyPlayingInPlayer(player: avPlayer)
print(finalURL, "<-- finalURL YEAH EYAHY YEAH?!?!?")
// newUserTakenVideo.videoURL = finalURL
let url = self.addVidToTempURL(vidURL: finalURL!)
newUserTakenVideo.videoURL = url
// let newUserTakenVideo = SelectedMedia(videoURL: finalURL, phAsset: (cell?.assetPH)!, thumbnailImg: (cell?.imageView.image)!, uniqueCellID: indexPath)
// GlobalSharedData.shared.arrayOfCurrentCreateMedia.append(newUserTakenVideo)
} else {
print("did noot work")
}
}
This is the function called:
func addVidToTempURL(vidURL: URL) -> URL? {
let fileManager = FileManager.default
let tempDir = fileManager.temporaryDirectory
let tempDirString = tempDir.path
do {
print( "tempDir: \(tempDir)" )
print( "tempDirString: \(tempDirString)" )
if fileManager.fileExists(atPath: tempDirString ) {
print( "tempDir exists" )
do {
try fileManager.createDirectory( at: tempDir, withIntermediateDirectories: true, attributes: nil )
print( "tempDir created" )
if fileManager.fileExists(atPath: tempDirString ) {
print( "tempDir exists" )
let vidData = try Data(contentsOf: vidURL)
fileManager.createFile(atPath: tempDirString, contents: vidData, attributes: nil)
print(tempDirString, " the stringfsdsda")
// fileManager.urls(for: fileManager.temporaryDirectory, in: fileManager.)
let url = URL(string: tempDirString)
return url
} else {
print( "tempDir STILL DOES NOT exist" )
return nil
}
} catch {
print( "tempDir NOT created" )
return nil
}
} else {
print( "tempDir DOES NOT exist" )
do {
try fileManager.createDirectory( at: tempDir, withIntermediateDirectories: true, attributes: nil )
print( "tempDir created" )
if fileManager.fileExists(atPath: tempDirString ) {
print( "tempDir exists" )
let vidData = try Data(contentsOf: vidURL)
fileManager.createFile(atPath: tempDirString, contents: vidData, attributes: nil)
print(tempDirString, " the fsdfdsdfsdfsfsd")
let url = URL(string: tempDirString)
return url
} else {
print( "tempDir STILL DOES NOT exist" )
return nil
}
} catch {
print( "tempDir NOT created" )
return nil
}
}
}
}
How can I get a URL reference to this files location?
I appreciate any help and can add more information if needed. Thanks, me
Since you don't seem to want your file to be visible to users or persisted between app launches, the Temporary directory sounds perfectly fine for your use case:
var tempVideoFileUrl: URL {
return FileManager.default.temporaryDirectory.appendingPathComponent("my_video_name")
}
func storeVideoToTemporaryFolder(videoData: Data) {
guard !FileManager.default.fileExists(atPath: tempVideoFileUrl.path) else {
return
}
do {
try videoData.write(to: tempVideoFileUrl)
}
catch {
fatalError()
}
}
func loadVideoFromTemporaryFolder() -> Data? {
if let data = try? Data(contentsOf: tempVideoFileUrl) {
return data
}
return nil
}
Worth mentioning though, the system may (and most likely will) purge this directory after the app is exited. It's recommended that you remove any temporary directories/files after they're no longer needed.
So in your case, you can simply remove it once you finished uploading to Firebase Storage:
func deleteVideoFromTemporaryFolder() {
do {
try FileManager.default.removeItem(at: videoFileUrl)
}
catch {
fatalError()
}
}
If you prefer to keep your file around between app launches though, you could use Application Support directory. But since Application Support and Documents directories gets automatically backed up, you may want to exclude your file from iCloud backup by setting its URL's isExcludedFromBackupKey key:
var applicationSupportVideoFileUrl: URL {
let applicationSupportFolderUrl = try! FileManager.default.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
return applicationSupportFolderUrl.appendingPathComponent("my_video_name")
}
func excludeFromCloudBackup(url: URL) {
var targetUrl = url
var isAlreadyExcludedFromBackup: Bool
do {
let storedRessourceValues = try targetUrl.resourceValues(forKeys: [URLResourceKey.isExcludedFromBackupKey])
isAlreadyExcludedFromBackup = storedRessourceValues.isExcludedFromBackup ?? false
}
catch {
fatalError()
}
guard !isAlreadyExcludedFromBackup else {
return
}
var ressourceValues = URLResourceValues()
ressourceValues.isExcludedFromBackup = true
do {
try targetUrl.setResourceValues(ressourceValues)
}
catch {
fatalError()
}
}
Edit: To get the data from your PHAsset, this should work:
import Photos
func loadVideoData(phAsset: PHAsset, completion: #escaping (Data?)->()) {
guard phAsset.mediaType == .video else {
return completion(nil)
}
let options = PHVideoRequestOptions()
options.isNetworkAccessAllowed = true
options.deliveryMode = .highQualityFormat
PHCachingImageManager().requestAVAsset(forVideo: phAsset, options: options) { (avAsset, _, _) in
guard let avUrlAsset = avAsset as? AVURLAsset else {
return
}
var videoData: Data?
do {
videoData = try Data(contentsOf: avUrlAsset.url)
} catch {
fatalError()
}
DispatchQueue.main.async {
completion(videoData)
}
}
}
Then simply call this method and store your video in the Temporary folder:
loadVideoData(phAsset: yourPhAsset) { [weak self] videoData in
guard let strongSelf = self else { return }
guard let videoData = videoData else {
return
}
strongSelf.storeVideoToTemporaryFolder(videoData: videoData)
}

How to download and save an audio file and then play it in swift?

I am trying to download an audio file from the internet and save it onto the phone. This is the download function:
func download() {
if let audioUrl = downloadUrl {
// 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()
}
}
}
Then, after I close and open the app, I use the following function to retrieve the url and play it using an AVPlayer:
func getUrl2() {
if let audioUrl = downloadUrl {
// then lets create your document folder url
let documentsDirectoryURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
destinationUrl = documentsDirectoryURL.appendingPathComponent(audioUrl.lastPathComponent)
if let u = self.destinationUrl {
let player = AVPlayer(url: u)
print(u)
print("Bouta play")
print(CMTimeGetSeconds(player.currentItem!.duration))
player.play()
}
}
}
The duration that keeps getting printed out is "nan". Is there a way to check if the audio file is actually downloading? Or could it be a problem with retrieving the file after the download? Thanks in advance.
First of all you have to check for the URL is not empty with the below logic:
if !link.isEmpty{
checkBookFileExists(withLink: link){ [weak self] downloadedURL in
guard let self = self else{
return
}
play(url: downloadedURL)
}
}
Then checkBookFileExists function will check if the file already saved or not before download it again:
func checkBookFileExists(withLink link: String, completion: #escaping ((_ filePath: URL)->Void)){
let urlString = link.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)
if let url = URL.init(string: urlString ?? ""){
let fileManager = FileManager.default
if let documentDirectory = try? fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor:nil, create: false){
let filePath = documentDirectory.appendingPathComponent(url.lastPathComponent, isDirectory: false)
do {
if try filePath.checkResourceIsReachable() {
print("file exist")
completion(filePath)
} else {
print("file doesnt exist")
downloadFile(withUrl: url, andFilePath: filePath, completion: completion)
}
} catch {
print("file doesnt exist")
downloadFile(withUrl: url, andFilePath: filePath, completion: completion)
}
}else{
print("file doesnt exist")
}
}else{
print("file doesnt exist")
}
}
Then if the file doesn't exists you will download it with the below function:
func downloadFile(withUrl url: URL, andFilePath filePath: URL, completion: #escaping ((_ filePath: URL)->Void)){
DispatchQueue.global(qos: .background).async {
do {
let data = try Data.init(contentsOf: url)
try data.write(to: filePath, options: .atomic)
print("saved at \(filePath.absoluteString)")
DispatchQueue.main.async {
completion(filePath)
}
} catch {
print("an error happened while downloading or saving the file")
}
}
}
That function will save it and you can play it with:
func play(url: URL) {
print("playing \(url)")
do {
audioPlayer = try AVAudioPlayer(contentsOf: url)
audioPlayer?.prepareToPlay()
audioPlayer?.delegate = self
audioPlayer?.play()
let percentage = (audioPlayer?.currentTime ?? 0)/(audioPlayer?.duration ?? 0)
DispatchQueue.main.async {
// do what ever you want with that "percentage"
}
} catch let error {
audioPlayer = nil
}
}

Error playing video using UIWebView

I am trying to play a video using UIWebView, but it's not showing any video even though the video is downloading from the server. Does anyone know what I'm doing wrong here?
Here is my code:
override func viewDidLoad() {
super.viewDidLoad()
self.pdfView.delegate = self
self.pdfView.mediaPlaybackRequiresUserAction = false
if "" != video?.videoPath {
self.loadFromUrl(path: (video?.videoPath)!)
self.activityIND.isHidden = true
self.activityIND.stopAnimating()
} else {
let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
let strName = video?.id
let filePath = "\(documentsPath)/"+strName!+".wmv"
let fileManager = FileManager.default
self.activityIND.startAnimating()
if fileManager.fileExists(atPath: filePath) {
self.loadFromUrl(path: filePath)
return;
}
let reference = FIRStorage.storage().reference(forURL: (self.video?.videoURL)!)
reference.data(withMaxSize: 50 * 1024 * 1024) { (data, error) -> Void in
if (error != nil) {
print ("unable to download video file from Firebase Storage")
self.activityIND.isHidden = false
self.activityIND.startAnimating()
} else {
if ((try! data?.write(to: URL.init(fileURLWithPath: filePath, isDirectory: false))) != nil) {
self.loadFromUrl(path: filePath)
print ("video file is downloaded from Firebase Storage")
self.db.upDate(id: (self.video?.id)!, videoPath: filePath)
self.activityIND.isHidden = true
}
}
}
}
}
func loadFromUrl(path: String)
{
let url = NSURL(string:path)
pdfView.loadRequest(NSURLRequest(url: url! as URL) as URLRequest)
activityIND.isHidden = true
activityIND.startAnimating()
}
turns out wmv format is not supported, once i changed the format to mp4 everything worked

AVAssetExportSession in share extension

I'm trying to use AVAssetExportSession on video selected in share extension and getting
Error Domain=NSURLErrorDomain Code=-3000 "Cannot create file"
UserInfo={NSLocalizedDescription=Cannot create file,
NSUnderlyingError=0x14811fdb0 {Error Domain=NSOSStatusErrorDomain
Code=-12124 "(null)"}}
But I can create file manually at the same NSURL without an error. Here is a function I'm using
func reencodeVideo() {
let videoAsset = AVURLAsset(URL: video.url)
let videoTrack = videoAsset.tracksWithMediaType(AVMediaTypeVideo)[0] as AVAssetTrack
print(videoTrack.estimatedDataRate)
let exportSession = AVAssetExportSession(asset: videoAsset, presetName: AVAssetExportPreset1920x1080)
guard let outputURL = uploadableFileURL else {
return
}
let fileManager = NSFileManager.defaultManager()
// let created = fileManager.createFileAtPath(outputURL.path!, contents: nil, attributes: nil)
if let path = outputURL.path where fileManager.fileExistsAtPath(path) {
print("file exists")
}
do {
try fileManager.removeItemAtURL(outputURL)
print("deleted")
} catch {
print(error)
}
exportSession?.outputURL = outputURL
exportSession?.outputFileType = AVFileTypeQuickTimeMovie
exportSession?.exportAsynchronouslyWithCompletionHandler{
print(exportSession?.status)
}
}
private var uploadableFileURL: NSURL? {
guard let tempFileName = video.url.lastPathComponent else {
return nil
}
let fileManager = NSFileManager.defaultManager()
guard let containerURL = fileManager.containerURLForSecurityApplicationGroupIdentifier(Constants.appGroupIdentifier) else {
return nil
}
return containerURL.URLByAppendingPathComponent("videoFile.mov")
}
I've successfully created file in the same directory, but AVAssetExportSession returns an error there.
Any ideas what I'm doing wrong?
I've tried using AVAssetReader and AVAssetWriter, and AVAssetWriter returns same error when trying to start. Encode process completes successfully if I'm using Documents directory and fails only when using shared app group container.
You're issue might be related to the use of the document folder and icloud sync.
See https://forums.developer.apple.com/message/77495#77495
If you do something like :
guard let containerURL = fileManager.containerURLForSecurityApplicationGroupIdentifier(Constants.appGroupIdentifier) else {
return nil
}
let libraryURL = containerURL.URLByAppendingPathComponent("Library", isDirectory: true)
let cachesURL = libraryURL.URLByAppendingPathComponent("Caches", isDirectory: true)
return cachesURL.URLByAppendingPathComponent("videoFile.mov")
I can't figure out from your code where your uploadableFileURL comes from, but the following works for me:
if let video = videoAsset {
let fm = NSFileManager.defaultManager().URLsForDirectory(.CachesDirectory, inDomains: .UserDomainMask)
let url = fm[0].URLByAppendingPathComponent("\(NSDate())").URLByAppendingPathExtension("mp4")
let exporter = AVAssetExportSession(asset: video, presetName: AVAssetExportPreset3840x2160)
exporter?.outputURL = url
exporter?.outputFileType = AVFileTypeMPEG4
exporter?.exportAsynchronouslyWithCompletionHandler() { _ in
let data = NSData(contentsOfURL: url)
}
}

Resources