I am using UIImagePickerController to record short (<30s) videos which are then saved and uploaded via our API. The app is cross-platform and so I need recorded videos to be encoded into mp4 format so that Android devices can play them.
I used instructions from the following questions to create my solution:
Swift - How to record video in MP4 format with UIImagePickerController?
AVFoundation record video in MP4 format
https://forums.developer.apple.com/thread/94762
I record my video through the UIImagePickerController like so:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
// Local variable inserted by Swift 4.2 migrator.
let info = convertFromUIImagePickerControllerInfoKeyDictionary(info)
let videoNSURL = info[convertFromUIImagePickerControllerInfoKey(UIImagePickerController.InfoKey.mediaURL)] as? NSURL
videoURL = videoNSURL!.absoluteURL
if let videoURL = videoURL {
let avAsset = AVURLAsset(url: videoURL, options: nil)
avAsset.exportVideo { (exportedURL) in
if let uploadVC = self.uploadVC {
uploadVC.incomingFileURL = exportedURL
uploadVC.myJewelleryID = self.myJewelleryID
uploadVC.topicID = self.topicID
}
DispatchQueue.main.async { [weak self] in
//Update UI with results from previous closure
self?.dismiss(animated: true, completion: nil)
self?.showUploadContainer()
self?.updateVideoContainerWithURL(url: exportedURL)
}
}
}
}
This then passes the exported MP4 url to the upload container view, where it saves the file to the device:
private func saveVideoFileToDevice() {
//Filename Struct = [AssetID]_[TopicID]_[CustomerID]_[Datestamp]
let date = Date()
let formater = DateFormatter()
formater.locale = Locale(identifier: "en_US_POSIX")
formater.dateFormat = "YYYY-MM-dd-HH-mm-ss"
uploadFileName = ""
if let mjID = myJewelleryID {
uploadFileName = "ASID_\(mjID)_\(User.instance.customerID)_\(formater.string(from: date)).mp4"
} else if let tID = topicID {
uploadFileName = "ASID_\(tID)_\(User.instance.customerID)_\(formater.string(from: date)).mp4"
}
let fileManager = FileManager.default
if let destURL = URL(string: "file://\(NSHomeDirectory())/Documents/\(uploadFileName!)") {
var fileData: Data!
print("destURL = \(destURL)")
do {
try fileManager.copyItem(at: incomingFileURL! as URL, to: destURL)
fileData = try Data(contentsOf: incomingFileURL! as URL)
try fileData.write(to: destURL)
}
catch {
print("DEBUG: Failed to save video data")
}
}
}
and then uploads the file to our API. Although the file is MP4, it does not play on Android. On inspection, the file looks very similar to a file that will actually play on an Android device when we compare the codec data:
Does anyone have any ideas on how I can fix this?
Thanks!
var exportSession: AVAssetExportSession!
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
picker.dismiss(animated: true, completion: nil)
guard let videoURL = (info[UIImagePickerController.InfoKey.mediaURL] as? URL) else { return }
encodeVideo(videoURL)
}
func encodeVideo(_ videoURL: URL) {
let avAsset = AVURLAsset(url: videoURL, options: nil)
//Create Export session
exportSession = AVAssetExportSession(asset: avAsset, presetName: AVAssetExportPresetPassthrough)
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] as URL
let filePath = documentsDirectory.appendingPathComponent("rendered-Video.mp4")
deleteFile(filePath)
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
DispatchQueue.main.async {
Utility.stopActivityIndicator()
switch self.exportSession!.status {
case .failed:
self.view.makeToast(self.exportSession?.error?.localizedDescription ?? "")
case .cancelled:
self.view.makeToast("Export canceled")
case .completed:
if let url = self.exportSession.outputURL {
//Rendered Video URL
}
default:
break
}
}
})
}
Delete File function:
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).")
}
}
Don't forget to import AVFoundation
Hope that will help!