Record iPhone video, Convert to mp4 and upload to PHP Server

I have an app that successfully records and uploads a file to my PHP server, but unfortunately the file either uploads as 0 bytes or won't play. I received guidance that I needed to use

AVAssetExportSession
to convert the file to
mp4
to get it to work but am having trouble incorporating this into my code correctly. Any help is greatly appreciated.


//Image Picker Code

func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
        print("Got a video")
   
        if let pickedVideo:URL = (info[UIImagePickerControllerMediaURL] as? URL) {
            /
            let selectorToCall = #selector(CameraVideoViewController.videoWasSavedSuccessfully(_:didFinishSavingWithError:context:))
            UISaveVideoAtPathToSavedPhotosAlbum(pickedVideo.relativePath, self, selectorToCall, nil)
            imageSelected = true
            uuid = UUID().uuidString
       
            if imageSelected == true {
                saveFileName = "video-\(uuid).mp4"
            }
            /
            let videoData = try? Data(contentsOf: pickedVideo)
            let paths = NSSearchPathForDirectoriesInDomains(
                FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)
            let documentsDirectory: URL = URL(fileURLWithPath: paths[0])
            let dataPath = documentsDirectory.appendingPathComponent(saveFileName)
            try! videoData?.write(to: dataPath, options: [])
            print("Saved to " + dataPath.absoluteString)
       
            imagePicker.dismiss(animated: true, completion: {
                /
                self.encodeVideo(dataPath as AnyObject)
                /
           
            })
        } }


// Convert to mp4 code

func encodeVideo(_ videoData: AnyObject)  {
    
        let avAsset = AVURLAsset(url: videoData as! URL, options: nil)
    
        let startDate = Foundation.Date()
    
        /
        let exportSession = AVAssetExportSession(asset: avAsset, presetName: AVAssetExportPresetPassthrough)
    
        /
        /
    
    
        let documentsDirectory = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
        let myDocumentPath = URL(fileURLWithPath: documentsDirectory).appendingPathComponent("temp.mp4").absoluteString
        let url = URL(fileURLWithPath: myDocumentPath)
    
        let documentsDirectory2 = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] as URL
    
        let filePath = documentsDirectory2.appendingPathComponent("rendered-Video.mp4")
        deleteFile(filePath)
    
        /
        if FileManager.default.fileExists(atPath: myDocumentPath) {
            do {
                try FileManager.default.removeItem(atPath: myDocumentPath)
            }
            catch let error {
                print(error)
            }
        }
    
    
    
        exportSession!.outputURL = filePath
        exportSession!.outputFileType = AVFileType.mp4
        exportSession!.shouldOptimizeForNetworkUse = true
        let start = CMTimeMakeWithSeconds(0.0, 0)
        let range = CMTimeRangeMake(start, avAsset.duration)
        exportSession?.timeRange = range
    
        exportSession!.exportAsynchronously(completionHandler: {() -> Void in
            switch exportSession!.status {
            case .failed:
                print("%@",exportSession?.error! as Any)
            case .cancelled:
                print("Export canceled")
            case .completed:
                /
                let endDate = Foundation.Date()
                let time = endDate.timeIntervalSince(startDate)
                print(time)
                print("Successful!")
                print(exportSession?.outputURL as Any)
                let mediaPath = exportSession?.outputURL?.path as String!
                print(mediaPath)
                self.uploadVideo(mediaPath! as String)
                /
                /
                /
            /
            default:
                break
            }
        
        })
    
    
    }


// Upload video

    func createBodyWithParamsVideo(_ parameters: [String: String]?, filePathKey: String?, mediaPath: Data, boundary: String) -> Data {
     
        var body = ""
     
        if let params = parameters {
            for (key, value) in params {
                body += "--\(boundary)\r\n"
                body += "Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n"
                body += "\(value)\r\n"
            }
        }
        var filename = ""
     
        if imageSelected {
            filename = "video-\(uuid).mp4"
        }
     
        let mimetype = "video/mp4"
        body += "--\(boundary)\r\n"
        body += "Content-Disposition: form-data; name=\"\(filePathKey!)\"; filename=\"\(filename)\"\r\n"
        body += "Content-Type: \(mimetype)\r\n\r\n"
        body += String(data: mediaPath, encoding: .utf8)!
        body += "\r\n"
     
        body += "--\(boundary)--\r\n"
     
        return Data(body.utf8)
     
    }
    /
    func uploadVideo(_ mediaPath: String) {
     
    
        let id = user!["id"] as! String
        uuid = UUID().uuidString
     
     
        let url = URL(string: "http:/
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
     
        let param = [
            "id" : id,
            "uuid" : uuid
        ]
        print("just passed videopost page")
        /
        let boundary = "Boundary-\(UUID().uuidString)"
        request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
        /
        /
        /
        let imageData = Data()
     
     
        /
        request.httpBody = createBodyWithParamsVideo(param, filePathKey: "file", mediaPath: imageData, boundary: boundary)
     
        /
        URLSession.shared.dataTask(with: request) { data, response, error in
         
            /
            DispatchQueue.main.async(execute: {
             
             
                if error == nil {
                 
                    do {
                     
                        /
                        let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
                     
                        /
                        guard let parseJSON = json else {
                            print("Error while parsing")
                            return
                        }
                     
                        /
                        let message = parseJSON["message"]
                     
                        /
                        if message != nil {
                         
                            /
                         
                            self.postBtn.alpha = 0.4
                            self.imageSelected = false
                         
                            /
                            self.tabBarController?.selectedIndex = 4
                         
                        }
                     
                    } catch {
                     
                        /
                        DispatchQueue.main.async(execute: {
                            let message = "\(error)"
                            appDelegate.infoView(message: message, color: colorSmoothRed)
                        })
                        return
                     
                    }
                 
                } else {
                 
                    /
                    DispatchQueue.main.async(execute: {
                        let message = error!.localizedDescription
                        appDelegate.infoView(message: message, color: colorSmoothRed)
                    })
                    return
                 
                }
             
             
            })
         
            }.resume()
     
    }

Accepted Reply

Hello techgirl08,

It looks like, on line 53, you are creating an empty Data object named "imageData". Then, you pass that to "createBodyWithParamsVideo" as the "mediaPath" parameter. That would explain the zero byte data. Was it supposed to be the actual image data?


Then you convert that zero byte data object into a string as the body. You don't need to do that. HTTP can handle binary data.


I strongly suggest looking for some library to properly construct a form upload request. Those can be tricky. If your data is very large, those are really, really tricky. You may need to deal with chunked data, interruptions, and resuming transfers.


An even better idea would be to use a REST server on the other end. Then you could do simpler PUT request as well as things like DELETE. There are many examples of REST servers in PHP or Node.

Replies

You need to split this problem up into parts:

  • Recording the video

  • Uploading the video

You can develop each of these independently. Specifically:

  • In the recording case, you can save the video to disk and then use Xcode’s container support to transfer the file to your Mac to verify that it was saved correctly.

  • In the uploading case, you can include a known good video file in your app’s bundle and then try uploading that.

If you have questions about recording, you should post them to Media > AVFoundation (Video and Camera). And if you have questions about uploading your should post them to Core OS > Networking.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks eskimo. My reason for including all functions is I'm not sure if I'm passing the correct variables across the functions to get the code to come together. The record works, the logs say the conversion is successful but a file is being uploaded but at 0 bytes.

Hello techgirl08,

It looks like, on line 53, you are creating an empty Data object named "imageData". Then, you pass that to "createBodyWithParamsVideo" as the "mediaPath" parameter. That would explain the zero byte data. Was it supposed to be the actual image data?


Then you convert that zero byte data object into a string as the body. You don't need to do that. HTTP can handle binary data.


I strongly suggest looking for some library to properly construct a form upload request. Those can be tricky. If your data is very large, those are really, really tricky. You may need to deal with chunked data, interruptions, and resuming transfers.


An even better idea would be to use a REST server on the other end. Then you could do simpler PUT request as well as things like DELETE. There are many examples of REST servers in PHP or Node.

John,
Thank you so much. Your tips helped and all issues are now resolved. You're the real MVP. 🙂