通过UIImagePickerController(),打开相册并选取相册内的资源,其实没什么难度。之前项目中需要将相册内的视频拿到本地APP中,并进行播放。具体思路为本地某个控制器准守UIImagePickerControllerDelegate代理方法,通过一下两个方法即可拿到当前视频的路径,尽管中间有通过264压缩,但是整体功能不受影响。
func initImagePickController(){
if UIImagePickerController.isSourceTypeAvailable(.photoLibrary) {
let picker = UIImagePickerController()
picker.delegate = self
picker.sourceType = UIImagePickerControllerSourceType.photoLibrary
picker.videoExportPreset = AVAssetExportPresetHighestQuality
picker.mediaTypes = [kUTTypeMovie as String]
picker.allowsEditing = false
self.present(picker, animated: true, completion: {
()-> Void in
})
}else{
print("读取相册错误")
}
}
// 代理方法,点击哪个视频,就会调用该方法。
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
let mediaType = info[UIImagePickerControllerMediaType] as! String
if mediaType == (kUTTypeMovie as String){
let videoURL = info[UIImagePickerControllerMediaURL] as! NSURL
let startTime : Int = 0
let urlStr = videoURL.absoluteString
self.dismiss(animated: true, completion: nil)
print(urlStr)
//拿到urlStr后,即可设置播放器的url。
}
}
上面的代码,在获取通过相机拍照或者其他APP保存的视频都没有问题。直到有一次,通过苹果家的“隔空投送”功能将Mac上的视频投送到iPhone相册里面,在提取该视频的时候程序崩溃了。
通过对比正确提取视频和错误提取后的info中的信息发现,能够正确提取的info[UIImagePickerControllerMediaURL]不为空,而引起崩溃的info[UIImagePickerControllerMediaURL]为nil。打断点后,在xcode的输出栏中,通过po info 命令,打印info的详细内容后发现:
正确:
▿ 3 elements
▿ 0 : 2 elements
- key : "UIImagePickerControllerReferenceURL"
- value : assets-library://asset/asset.MP4?id=CCCE2E1E-15F3-4416-8E73-B6102ACDEC15&ext=MP4
▿ 1 : 2 elements
- key : "UIImagePickerControllerMediaType"
- value : public.movie
▿ 2 : 2 elements
- key : "UIImagePickerControllerMediaURL"
- value : file:///private/var/mobile/Containers/Data/Application/ED79D02E-3516-4B82-B0F7-EA79BCF48903/tmp/7B922446-27CB-46F9-BB7E-0C3273CE45D8.MOV
错误:
▿ 2 elements
▿ 0 : 2 elements
- key : "UIImagePickerControllerReferenceURL"
- value : assets-library://asset/asset.MOV?id=7CE3AC64-2EF7-4915-B257-3D140B9F3919&ext=MOV
▿ 1 : 2 elements
- key : "UIImagePickerControllerMediaType"
- value : public.movie
通过正常提取视频路径的后发现,通过UIImagePickerControllerMediaURL这个key,指向的是通过压缩后放入的视频路径。而UIImagePickerControllerReferenceURL是指向源视频资源的一个ID。通过某些方法,就可以将源视频提取出来。
那么我们可以通过UIImagePickerControllerReferenceURL这个信息把视频源提取出来,复制到本地app的一个专属临时文件夹内,然后播放该视频就可以了吧。这样还可以避免了从相册里面取视频时必须压缩的尴尬局面。
那么修改后成了一下代码:
if let referenceURL = info[UIImagePickerControllerReferenceURL] as? NSURL {
let fetchResult = PHAsset.fetchAssets(withALAssetURLs: [referenceURL as URL], options: nil)
if let phAsset = fetchResult.firstObject {
PHImageManager.default().requestAVAsset(forVideo: phAsset, options: PHVideoRequestOptions(), resultHandler: { (asset, audioMix, info) -> Void in
DispatchQueue.main.async {
if let asset = asset as? AVURLAsset {
let videoData = NSData(contentsOf: asset.url)
// optionally, write the video to the temp directory
let videoPath = NSTemporaryDirectory() + "tmpMovie.MOV"
let videoURL = NSURL(fileURLWithPath: videoPath)
let writeResult = videoData?.write(to: videoURL as URL, atomically: true)
if writeResult == true{
print(videoURL)
// 利用该videoURL获取复制到本地APP的视频资源路径,上传、下载作为播放器的视频源
}else{
print("失败")
}
}
}
})
}
}
这样做其实基本实现了之前设想的功能,但是xcode却报出了警告,大体意思就是UIImagePickerControllerReferenceURL方法已经过时,请利用UIImagePickerControllerPHAsset。但是通过对比上面的正确和错误的info信息知道,根本没有这个key键啊?怎么取出。
原来在iOS11后,苹果对于用户隐私做出了某些改变,既能保护了用户的隐私,又能方便app开发者方便调用相册资源。
iOS11之前,利用UIImagePickerController后会弹出授权窗口,意思是要访问你的相册,需要经过你的同意。反正就是只要是你想要访问相册,无论该访问有多么的微不足道,都会弹出窗口。但是在iOS11后,创建UIImagePickerController,并且present后,不在弹出,即本博客的func initImagePickController()方法执行过程不在弹出弹窗。
而之前之所以无法通过info[UIImagePickerControllerPHAsset]获取的资源就是因为这方面的原因。通过断点发现,执行上面的代码段:PHAsset.fetchAssets()该方法时(xcode此时也提示该方法已经过时),会弹出授权窗口。利用到了PHAsset类。经过授权以后,info[UIImagePickerControllerPHAsset]不在为nil,此时打印info这个字典的信息为:
▿ 3 elements
▿ 0 : 2 elements
- key : "UIImagePickerControllerReferenceURL"
- value : assets-library://asset/asset.MP4?id=9ABEAACE-54DD-4C2F-BCEE-DEDFA12B2BDC&ext=MP4
▿ 1 : 2 elements
- key : "UIImagePickerControllerPHAsset"
- value : <PHAsset: 0x103d823e0> 9ABEAACE-54DD-4C2F-BCEE-DEDFA12B2BDC/L0/001 mediaType=2/0, sourceType=1, (528x960), creationDate=2019-05-05 09:31:04 +0000, location=0, hidden=0, favorite=0
▿ 2 : 2 elements
- key : "UIImagePickerControllerMediaType"
- value : public.movie
此时发现,没有了UIImagePickerControllerMediaURL该key键。有了UIImagePickerControllerPHAsset该key键,获取对应的PHAsset类就可以提取对应的资源了。
经过上面的分析可知,我们在正确的提取相册内的资源并复制到本地APP内,在点击选中视频后,在执行UIImagePickerControllerDelegate的imagePickerController()代理方法之前,必须获取访问相册的权限后才能通过info[UIImagePickerControllerPHAsset]得到PHAsset。由此整理出一下代码:
func jumpToViewController() {
// 主动获取打开系统相册权限
if PHPhotoLibrary.authorizationStatus() == .notDetermined{
PHPhotoLibrary.requestAuthorization { (state) in
if state == .authorized{
self.initImagePickController()
} else if state == .denied || state == .restricted{
self.dismiss(animated: true, completion: {
print("拒绝访问相册")
})
}
}
}else{
self.initImagePickController()
}
}
// 也可以主动调用UIImageWriteToSavedPhotosAlbum()该方法,即向相册内写东西,但是我们内部可以什么都不写,只是为了获取权限。
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
if let asset = info[UIImagePickerControllerPHAsset] as? PHAsset {
PHImageManager.default().requestAVAsset(forVideo: asset, options: PHVideoRequestOptions(), resultHandler: { (asset, audioMix, info) -> Void in
DispatchQueue.main.async {
if let asset = asset as? AVURLAsset {
let videoData = NSData(contentsOf: asset.url)
// optionally, write the video to the temp directory
let videoPath = NSTemporaryDirectory() + "tmpMovie.MOV"
let videoURL = NSURL(fileURLWithPath: videoPath)
let writeResult = videoData?.write(to: videoURL as URL, atomically: true)
if writeResult == true{
print(videoURL)
// 利用该videoURL获取复制到本地APP的视频资源路径,上传、下载作为播放器的视频源
}else{
print("失败")
}
}
}
})
}
复制文件的时候,必须在主线程中进行,否则会报错:DispatchQueue : Cannot be called with asCopy = NO on non-main thread。