Question or problem with Swift language programming:
I am creating a app in which i need to record videos and upload it to a server. Now my project has a android version too. To support android version i have to record the videos in mp4 format. I followed this tutorial to set the UIImagePicker media type to movie format imagePicker.mediaTypes = [kUTTypeMovie as String]
The UIImagePickerController is perfect for my requirement and the only thing that i need to change is its saving format to mp4. I tried kUTTypeMPEG4 in mediaTypes but it throws error at the run time with no error description.
This is my video Capture function
func startCameraFromViewController() { if UIImagePickerController.isSourceTypeAvailable(.Camera) == false { return } viewBlack.hidden = false presentViewController(cameraController, animated: false, completion: nil) cameraController.sourceType = .Camera cameraController.mediaTypes = [kUTTypeMovie as String] //cameraController.mediaTypes = [kUTTypeMPEG4 as String] cameraController.cameraCaptureMode = .Video cameraController.videoQuality = .TypeMedium if(getPurchaseId() as! Int == 0) { if(txtBenchMark.text?.isEmpty == false) { cameraController.videoMaximumDuration = NSTimeInterval(300.0) }else{ cameraController.videoMaximumDuration = NSTimeInterval(60.0) } }else{ cameraController.videoMaximumDuration = NSTimeInterval(600.0) } cameraController.allowsEditing = false }
I am using Swift 2.2 and Xcode 8 with Use Legacy swift Language version = Yes
Any Alternative Solutions are also appreciated. Thanks in advance.
EDIT:
I found out that there is no method to directly record videos in mp4 format in swift. only can be converted to required format from apple’s quicktime mov format.
How to solve the problem:
Solution 1:
Here is some code that you can use to convert the recorded video into MP4:
func encodeVideo(videoURL: NSURL) { let avAsset = AVURLAsset(URL: videoURL, options: nil) var startDate = NSDate() //Create Export session exportSession = AVAssetExportSession(asset: avAsset, presetName: AVAssetExportPresetPassthrough) // exportSession = AVAssetExportSession(asset: composition, presetName: mp4Quality) //Creating temp path to save the converted video let documentsDirectory = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] let myDocumentPath = NSURL(fileURLWithPath: documentsDirectory).URLByAppendingPathComponent("temp.mp4").absoluteString let url = NSURL(fileURLWithPath: myDocumentPath) let documentsDirectory2 = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0] as NSURL let filePath = documentsDirectory2.URLByAppendingPathComponent("rendered-Video.mp4") deleteFile(filePath) //Check if the file already exists then remove the previous file if NSFileManager.defaultManager().fileExistsAtPath(myDocumentPath) { do { try NSFileManager.defaultManager().removeItemAtPath(myDocumentPath) } catch let error { print(error) } } url exportSession!.outputURL = filePath exportSession!.outputFileType = AVFileTypeMPEG4 exportSession!.shouldOptimizeForNetworkUse = true var start = CMTimeMakeWithSeconds(0.0, 0) var range = CMTimeRangeMake(start, avAsset.duration) exportSession.timeRange = range exportSession!.exportAsynchronouslyWithCompletionHandler({() -> Void in switch self.exportSession!.status { case .Failed: print("%@",self.exportSession?.error) case .Cancelled: print("Export canceled") case .Completed: //Video conversion finished var endDate = NSDate() var time = endDate.timeIntervalSinceDate(startDate) print(time) print("Successful!") print(self.exportSession.outputURL) default: break } }) } func deleteFile(filePath:NSURL) { guard NSFileManager.defaultManager().fileExistsAtPath(filePath.path!) else { return } do { try NSFileManager.defaultManager().removeItemAtPath(filePath.path!) }catch{ fatalError("Unable to delete file: \(error) : \(__FUNCTION__).") } }
Source: https://stackoverflow.com/a/39329155/4786204
Solution 2:
I made some modifications to the following 2 answers to make it compatible with Swift3:
https://stackoverflow.com/a/40354948/2470084
https://stackoverflow.com/a/39329155/2470084
import AVFoundation func encodeVideo(videoURL: URL){ let avAsset = AVURLAsset(url: videoURL) let startDate = Date() let exportSession = AVAssetExportSession(asset: avAsset, presetName: AVAssetExportPresetPassthrough) let docDir = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] let myDocPath = NSURL(fileURLWithPath: docDir).appendingPathComponent("temp.mp4")?.absoluteString let docDir2 = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] as NSURL let filePath = docDir2.appendingPathComponent("rendered-Video.mp4") deleteFile(filePath!) if FileManager.default.fileExists(atPath: myDocPath!){ do{ try FileManager.default.removeItem(atPath: myDocPath!) }catch let error{ print(error) } } exportSession?.outputURL = filePath exportSession?.outputFileType = AVFileTypeMPEG4 exportSession?.shouldOptimizeForNetworkUse = true let start = CMTimeMakeWithSeconds(0.0, 0) let range = CMTimeRange(start: start, duration: avAsset.duration) exportSession?.timeRange = range exportSession!.exportAsynchronously{() -> Void in switch exportSession!.status{ case .failed: print("\(exportSession!.error!)") case .cancelled: print("Export cancelled") case .completed: let endDate = Date() let time = endDate.timeIntervalSince(startDate) print(time) print("Successful") print(exportSession?.outputURL ?? "") default: break } } } func deleteFile(_ filePath:URL) { guard FileManager.default.fileExists(atPath: filePath.path) else{ return } do { try FileManager.default.removeItem(atPath: filePath.path) }catch{ fatalError("Unable to delete file: \(error) : \(#function).") } }
Solution 3:
A quick swift 4 update to the previous answers:
func encodeVideo(videoUrl: URL, outputUrl: URL? = nil, resultClosure: @escaping (URL?) -> Void ) { var finalOutputUrl: URL? = outputUrl if finalOutputUrl == nil { var url = videoUrl url.deletePathExtension() url.appendPathExtension(".mp4") finalOutputUrl = url } if FileManager.default.fileExists(atPath: finalOutputUrl!.path) { print("Converted file already exists \(finalOutputUrl!.path)") resultClosure(finalOutputUrl) return } let asset = AVURLAsset(url: videoUrl) if let exportSession = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetPassthrough) { exportSession.outputURL = finalOutputUrl! exportSession.outputFileType = AVFileType.mp4 let start = CMTimeMakeWithSeconds(0.0, 0) let range = CMTimeRangeMake(start, asset.duration) exportSession.timeRange = range exportSession.shouldOptimizeForNetworkUse = true exportSession.exportAsynchronously() { switch exportSession.status { case .failed: print("Export failed: \(exportSession.error != nil ? exportSession.error!.localizedDescription : "No Error Info")") case .cancelled: print("Export canceled") case .completed: resultClosure(finalOutputUrl!) default: break } } } else { resultClosure(nil) } }
Solution 4:
Swift 5.2 Update Solution
// Don't forget to import AVKit func encodeVideo(at videoURL: URL, completionHandler: ((URL?, Error?) -> Void)?) { let avAsset = AVURLAsset(url: videoURL, options: nil) let startDate = Date() //Create Export session guard let exportSession = AVAssetExportSession(asset: avAsset, presetName: AVAssetExportPresetPassthrough) else { completionHandler?(nil, nil) return } //Creating temp path to save the converted video let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] as URL let filePath = documentsDirectory.appendingPathComponent("rendered-Video.mp4") //Check if the file already exists then remove the previous file if FileManager.default.fileExists(atPath: filePath.path) { do { try FileManager.default.removeItem(at: filePath) } catch { completionHandler?(nil, error) } } exportSession.outputURL = filePath exportSession.outputFileType = AVFileType.mp4 exportSession.shouldOptimizeForNetworkUse = true let start = CMTimeMakeWithSeconds(0.0, preferredTimescale: 0) let range = CMTimeRangeMake(start: start, duration: avAsset.duration) exportSession.timeRange = range exportSession.exportAsynchronously(completionHandler: {() -> Void in switch exportSession.status { case .failed: print(exportSession.error ?? "NO ERROR") completionHandler?(nil, exportSession.error) case .cancelled: print("Export canceled") completionHandler?(nil, nil) case .completed: //Video conversion finished let endDate = Date() let time = endDate.timeIntervalSince(startDate) print(time) print("Successful!") print(exportSession.outputURL ?? "NO OUTPUT URL") completionHandler?(exportSession.outputURL, nil) default: break } }) }
Solution 5:
Minor refactoring of previous examples:
import AVFoundation extension AVURLAsset { func exportVideo(presetName: String = AVAssetExportPresetHighestQuality, outputFileType: AVFileType = .mp4, fileExtension: String = "mp4", then completion: @escaping (URL?) -> Void) { let filename = url.deletingPathExtension().appendingPathExtension(fileExtension).lastPathComponent let outputURL = FileManager.default.temporaryDirectory.appendingPathComponent(filename) if let session = AVAssetExportSession(asset: self, presetName: presetName) { session.outputURL = outputURL session.outputFileType = outputFileType let start = CMTimeMakeWithSeconds(0.0, 0) let range = CMTimeRangeMake(start, duration) session.timeRange = range session.shouldOptimizeForNetworkUse = true session.exportAsynchronously { switch session.status { case .completed: completion(outputURL) case .cancelled: debugPrint("Video export cancelled.") completion(nil) case .failed: let errorMessage = session.error?.localizedDescription ?? "n/a" debugPrint("Video export failed with error: \(errorMessage)") completion(nil) default: break } } } else { completion(nil) } } }
Also: AVAssetExportPresetHighestQuality
preset works when video is played on Android / Chrome.
P.S. Be aware that the completion handler of exportVideo
method might not be returned on the main thread.