上传到 PHP 服务器后,录制的 iPhone X 视频将无法播放
Posted
技术标签:
【中文标题】上传到 PHP 服务器后,录制的 iPhone X 视频将无法播放【英文标题】:Recorded iPhone X videos won't play after upload to PHP Server 【发布时间】:2017-12-20 16:41:28 【问题描述】:根据建议解决方案修改了代码,但仍然无法正常工作。视频将上传为0字节。我有一个应用程序,用户应该能够从手机录制视频,选择“使用视频”时,它将视频上传到我们的php服务器。文件上传成功,根据显示的大小,它不是空的。但是,当我在应用程序中播放视频时,甚至直接通过浏览器播放视频时,它都不会播放。播放代码没有问题,因为我已经硬编码了指向其他网站上其他视频的链接,并且效果很好。代码如下,非常感谢任何帮助。
// 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转换
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)
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()
【问题讨论】:
ios 11 添加en.wikipedia.org/wiki/High_Efficiency_Video_Coding 作为新的默认视频格式。 感谢 ceejayoz,我开始阅读这篇文章,但不清楚实际的文件扩展名是什么。你碰巧知道吗? 我不认为这是一个新的文件扩展名,只是一个新的编解码器。.mov
或 .mp4
可能。
您为什么不将您的视频重新编码为真正的 MP4?您收到的视频可能是 HEC。您可能想使用AVAssetExportSession
来执行此操作。
@techgirl08 能否请您上传更新后的代码,以便我测试可能有什么问题?
【参考方案1】:
好的,我确实编写了一些代码来实际编码和上传视频。
不幸的是,在URLSession
中做一个multipart/form-data
实际上很难,所以我使用Alamofire
来实际上传视频。
这是部分代码:
UIImagePickerControllerDelegate(这是你应该修改的,检查//TODO cmets)
// 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 安装)
// 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
现在,要使其正常工作,您显然必须录制视频、导入框架并指定您的视图控制器符合协议,所以:
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)
希望对你有用,欢迎提问。
【讨论】:
以上是关于上传到 PHP 服务器后,录制的 iPhone X 视频将无法播放的主要内容,如果未能解决你的问题,请参考以下文章
我将视频与其他视频合并后将其上传到服务器,但录制的视频顺时针旋转
iOS - 使用 AVFoundation 录制声音后 iPhone 声音级别低