上传到 PHP 服务器后无法播放录制的 iPhone X 视频 [英] Recorded iPhone X videos won't play after upload to PHP Server
问题描述
代码已根据建议解决方案进行了修订,但仍然无法正常工作.视频将上传为0字节.我有一个应用程序,用户应该能够从手机录制视频,选择使用视频"时,它将视频上传到我们的PHP服务器.文件上传成功,根据显示的大小,它不是空的.但是,当我在应用程序中甚至直接通过浏览器播放视频时,它不会播放.播放代码没有问题,因为我已经对其他网站上的其他视频的链接进行了硬编码,并且效果很好.代码如下,非常感谢任何帮助.
Code Revised based on suggestion solution but still not working. Videos are uploading as 0 bytes. I have an app where a user should be able to record a video from their phone and when 'Use Video' is selected it uploads the video to our PHP Server. The file is uploading successfully and based on the size shown it is not empty. However when I go to play the video in the app or even directly through the browser it won't play. There's no issue with the playback code as I've hardcoded links to other videos on other sites and it works fine. Code is below, any help is much appreciated.
// Finished recording a video
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
print("Got a video")
if let pickedVideo:URL = (info[UIImagePickerControllerMediaURL] as? URL) {
// Save video to the main photo album
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"
}
// Save the video to the app directory so we can play it later
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: {
// Anything you want to happen when the user saves an video
self.encodeVideo(dataPath: dataPath)
self.uploadVideo(videoData!)
})
} }
//视频的 MP4 转换
// MP4 Conversion of video
func encodeVideo(dataPath: URL){
let avAsset = AVURLAsset(url: dataPath)
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")
//uploadVideo(filePath)
//self.encodeVideo(dataPath: dataPath)
deleteFile(filePath!)
if FileManager.default.fileExists(atPath: myDocPath!){
do{
try FileManager.default.removeItem(atPath: myDocPath!)
}catch let error{
print(error)
}
}
//self.uploadVideo((myDocPath as AnyObject) as! URL)
exportSession?.outputURL = filePath
exportSession?.outputFileType = AVFileType.mp4
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).")
}
}
//创建参数
func createBodyWithParams(_ parameters: [String: String]?, filePathKey: String?, videoData: 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: videoData, encoding: .utf8)!
body += "\r\n"
body += "--\(boundary)--\r\n"
return Data(body.utf8)
}
//函数向 PHP 发送上传文件的请求func uploadVideo(_ videoData: Data) {
// function sending request to PHP to upload a file func uploadVideo(_ videoData: Data) {
let id = user!["id"] as! String
uuid = UUID().uuidString
let url = URL(string: "http://www.foo.com/videoposts.php")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
let param = [
"id" : id,
"uuid" : uuid
]
// body
let boundary = "Boundary-\(UUID().uuidString)"
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
// if picture is selected, compress it by half
let imageData = Data()
// ... body
request.httpBody = createBodyWithParams(param, filePathKey: "file", videoData: imageData, boundary: boundary)
// launch session
URLSession.shared.dataTask(with: request) { data, response, error in
// get main queu to communicate back to user
DispatchQueue.main.async(execute: {
if error == nil {
do {
// json containes $returnArray from php
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
// declare new var to store json inf
guard let parseJSON = json else {
print("Error while parsing")
return
}
// get message from $returnArray["message"]
let message = parseJSON["message"]
// if there is some message - post is made
if message != nil {
// reset UI
self.postBtn.alpha = 0.4
self.imageSelected = false
// switch to another scene
self.tabBarController?.selectedIndex = 4
}
} catch {
// get main queue to communicate back to user
DispatchQueue.main.async(execute: {
let message = "\(error)"
appDelegate.infoView(message: message, color: colorSmoothRed)
})
return
}
} else {
// get main queue to communicate back to user
DispatchQueue.main.async(execute: {
let message = error!.localizedDescription
appDelegate.infoView(message: message, color: colorSmoothRed)
})
return
}
})
}.resume()
}
推荐答案
好的,我编写了一些代码来实际编码和上传视频.不幸的是,在 URLSession
中做一个 multipart/form-data
实际上非常困难,所以我使用 Alamofire
来实际上传视频.
Okay, I did code something to actually encode and upload a video.
Unfortunately, doing a multipart/form-data
in URLSession
is actually pretty hard, so I used Alamofire
to actually upload the video.
这是部分代码:
UIImagePickerControllerDelegate(这是你应该修改的,查看//TODO 注释)
UIImagePickerControllerDelegate (this is the one you should modify, check the //TODO comments)
// MARK: UIImagePickerControllerDelegate
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
print("Cancelled video operation.")
dismiss(animated: true, completion: nil)
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
guard let mediaType = info[UIImagePickerControllerMediaType] as? String else {
print("Error with media type. Cancelling.")
dismiss(animated: true, completion: nil)
return;
}
print("Media type: \(mediaType)")
if ( mediaType == "public.movie" ) { // Video selected
let videoURL: URL
if info[UIImagePickerControllerMediaURL] != nil {
videoURL = info[UIImagePickerControllerMediaURL] as! URL
}
else {
videoURL = info[UIImagePickerControllerReferenceURL] as! URL
}
if ( picker.sourceType == .camera ) {
// The original video came from the camera, so it's considered new
// Save it to the photo library
saveVideo(url: videoURL, albumName: "MyApp")
}
// Dismiss the media picker and then re-encode the video
dismiss(animated: true) {
self.exportVideoToMP4(url: videoURL) { (exportedVideoURL) in
guard let tempURL = exportedVideoURL else {
print("ERROR: Unknown error. The exported video URL is nil.")
return
}
print("Temporary video successfully exported to: \(tempURL.absoluteString)")
// TODO: Add your own POST parameters
let uuid = UUID().uuidString
let params = [
"uuid" : uuid,
"user" : "myUserNameOrWhatever"
]
// TODO: Change the parameters for uploading
self.upload( to: "http://yourweb.com/uploadVideo.php", // The URL to send the upload to
videoURL: tempURL, // The file URL of the temporary video file
parameters: params, // The POST parameters you want to send along with the upload
fileName: "vid-\(uuid).mp4", // The filename you want the server to receive.
fieldName: "video_file" // This is "name" from <input type="file" name="video_file" ... /> in HTML
) { (response) in
guard let resp = response else {
print("ERROR: Empty or unrecognizable response from server.")
return
}
print("Video uploaded. RESPONSE: \(resp)")
//: TODO Parse the server response after uploading
}
}
}
}
}
照片库辅助方法
// MARK: Photo Library
func saveVideo(url: URL, albumName: String) {
// Check authorization status before trying to save the video
switch PHPhotoLibrary.authorizationStatus() {
case .notDetermined:
PHPhotoLibrary.requestAuthorization() { (status) in
switch status {
case .authorized:
self.saveVideo(url: url, albumName: albumName) // Re-try to save the video after authorization
return
default:
return
}
}
case .authorized:
// Save the video to the Photo Library here
if let assetCollection = assetCollection(albumName: albumName) {
// Asset collection exists, insert directly
insertVideo(url: url, assetCollection: assetCollection)
}
else {
// Asset collection doesn't exist, create it and then insert
PHPhotoLibrary.shared().performChanges({
PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle: albumName)
}, completionHandler: { (success, error) in
guard success else {
print("ERROR: \(error!.localizedDescription)")
return
}
let createdAssetCollection = self.assetCollection(albumName: albumName)!
self.insertVideo(url: url, assetCollection: createdAssetCollection)
})
}
return
default:
// Not authorized
print("Not authorized to save a video to the Photo Library.")
return
}
}
func assetCollection(albumName: String) -> PHAssetCollection? {
let fetchOptions = PHFetchOptions()
fetchOptions.predicate = NSPredicate(format:"title == '\(albumName)'")
let fetchResult = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .albumRegular, options: fetchOptions)
return fetchResult.firstObject
}
func insertVideo(url: URL?, assetCollection: PHAssetCollection) {
guard let videoURL = url else {
print("ERROR: The URL to insert into the Photo Library is empty.")
return
}
PHPhotoLibrary.shared().performChanges({
let createAssetRequest = PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: videoURL)
let assetPlaceholder = createAssetRequest?.placeholderForCreatedAsset
let changeRequest = PHAssetCollectionChangeRequest(for: assetCollection)
let enumeration: NSArray = [assetPlaceholder!]
changeRequest?.addAssets(enumeration)
}, completionHandler: { (success, error) in
guard success else {
print("ERROR: \(error!.localizedDescription)")
return
}
print("Video saved successfully to the Photo Library album.")
})
}
视频上传(使用 Alamofire
,从 CocoaPods 安装)
Video Uploading (using Alamofire
, installed from CocoaPods)
// MARK: Video upload
func upload(to uploadAddress: String, videoURL: URL, parameters: [String:Any]?, fileName: String, fieldName: String, _ completion: ((String?) -> Void)?) {
Alamofire.upload(multipartFormData: { (multipartFormData) in
// Add the video file (if data is correct)
if let videoData = FileManager.default.contents(atPath: videoURL.path) {
multipartFormData.append(videoData, withName: fileName)
}
// Add the post params (if available)
if let params = parameters {
for (key, value) in params {
multipartFormData.append( (value as! String).data(using: .utf8)! , withName: key)
}
}
}, to: uploadAddress)
{ (result) in
switch result {
case .success(let upload, _, _):
upload.responseString { (response) in
if let completionHandler = completion {
completionHandler(response.result.value)
}
}
case .failure(let encodingError):
print("ERROR: \(encodingError.localizedDescription)")
if let completionHandler = completion {
completionHandler(nil)
}
}
}
}
AVFoundation(编码方式)
// MARK: AVFoundation
func exportVideoToMP4(url: URL, _ completion: @escaping ((URL?) -> Void)) {
// Show some sort of indicator here, as this could take a while
// Generate a temporary URL path to export the video
let relativePath = "myAppTempVideoExport.mp4";
let outputFilePath = NSTemporaryDirectory() + relativePath;
print("Temp file path: \(outputFilePath)")
// If there's any temp file from before at that path, delete it
if FileManager.default.fileExists(atPath: outputFilePath) {
do {
try FileManager.default.removeItem(atPath: outputFilePath)
}
catch {
print("ERROR: Can't remove temporary file from before. Cancelling export.")
completion(nil)
return
}
}
// Export session setup
let outputFileURL = URL(fileURLWithPath: outputFilePath)
let asset = AVAsset(url: url) // Original (source) video
// The original video codec is probably HEVC, so we'll force the system to re-encode it at the highest quality in MP4
// You probably want to use medium quality if this video is intended to be uploaded (as this example is doing)
if let exportSession = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetHighestQuality) {
exportSession.outputURL = outputFileURL
exportSession.outputFileType = .mp4
exportSession.exportAsynchronously {
// Hide the indicator for the export session
switch exportSession.status {
case .completed:
print("Video export completed.")
completion(outputFileURL)
return
case .failed:
print("ERROR: Video export failed. \(exportSession.error!.localizedDescription)")
completion(nil)
return
case .cancelled:
print("Video export cancelled.")
completion(nil)
return
default:
break
}
}
}
else {
print("ERROR: Cannot create an AVAssetExportSession.")
return
}
}
现在,要让它工作,您显然必须录制视频、导入框架并指定您的视图控制器符合协议,因此:
Now, for this to work, you obviously have to record a video, import frameworks and specify that your view controller conforms to protocols, so:
import AVFoundation
import Photos
import Alamofire
class ViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
let imagePicker = UIImagePickerController()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
imagePicker.sourceType = .camera
imagePicker.delegate = self
imagePicker.showsCameraControls = true
imagePicker.allowsEditing = true
imagePicker.mediaTypes = ["public.movie"]
DispatchQueue.main.async {
self.present(self.imagePicker, animated: true, completion: nil)
}
}
}
希望对你有用,欢迎提问.
I hope this is useful to you, feel free to ask.
这篇关于上传到 PHP 服务器后无法播放录制的 iPhone X 视频的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!