I have simple app which displays all videos from the photolibrary of iPhone (using PHAsset).
I'm able to display thumbnail of all videos (in collection view) but when I tap any video it plays same video only, I did not hardcode any url.
Below is the code, not sure where I'm wrong.
getting the video assets
var va: PHFetchResult<PHAsset>!
override func viewDidLoad() {
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [ NSSortDescriptor(key: "creationDate", ascending: false) ]
fetchOptions.predicate = NSPredicate(format: "mediaType = %d", PHAssetMediaType.video.rawValue)
va = PHAsset.fetchAssets(with: fetchOptions)
}
When tapping the video thumbnail in the collection view, creating new UIViewController and passing the asset data
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print(indexPath)
let vc=videoVC()
vc.vasset = self.va
vc.passedContentOffset = indexPath
self.navigationController?.pushViewController(vc, animated: true)
}
Finally playing the video
let Newasset = allAssets1[indexPath.row]
PHCachingImageManager().requestAVAsset(forVideo: Newasset, options: nil) { (Newasset, audioMix, args) in
let asset1 = asset as! AVURLAsset
DispatchQueue.main.async {
let player = AVPlayer(url: asset1.url)
let playerViewController = AVPlayerViewController()
playerViewController.player = player
self.present(playerViewController, animated: true) {
playerViewController.player!.play()
}
}
}
Really not sure where code is going wrong from my end.
you probably need to check the indexPath that you are passing and this should work.
Change
let Newasset = allAssets1[indexPath.row]
to
let Newasset = allAssets1[indexPath.item]
Related
I'm trying to get all videos that are in the camera roll on a user's phone when they try and upload a video, but I'm not sure how to.
I've done this to get all pictures and noticed that if I change the .image to .video it gets all the videos, but they are still presented as an image and you can't play the video:
func fetchImagesFromDeviceLibary() {
let allPhotos = PHAsset.fetchAssets(with: .image, options: getAssetFetchOptions())
DispatchQueue.global(qos: .background).async {
//Enumerate objects
allPhotos.enumerateObjects({ (asset, count, stop) in
let imageManager = PHImageManager.default()
let targetSize = CGSize(width: 600, height: 600)
let options = PHImageRequestOptions()
options.isSynchronous = true
imageManager.requestImage(for: asset, targetSize: targetSize, contentMode: .aspectFit, options: options, resultHandler: {
(image, info) in
if let image = image {
self.videos.append(image)
self.assets.append(asset)
if self.selectedVideo == nil {
self.selectedVideo = image
}
if count == allPhotos.count - 1 {
DispatchQueue.main.async {
self.collectionView.reloadData()
}
}
}
})
})
}
}
func getAssetFetchOptions() -> PHFetchOptions {
let options = PHFetchOptions()
options.fetchLimit = 50
let sortDescriptor = NSSortDescriptor(key: "creationDate", ascending: false)
options.sortDescriptors = [sortDescriptor]
return options
}
How would I get all the videos and display them on screen so that you can interact with them?
After changing fetchAssets with .image to .video make the required changes in the getAssetsFetchOption().
func getAssetFetchOptions() -> PHFetchOptions {
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate",
ascending: false)]
// For Images Only
// fetchOptions.predicate = NSPredicate(format: mediaType == %d", PHAssetMediaType.image.rawValue)
// For Videos Only
// fetchOptions.predicate = NSPredicate(format: "mediaType == %d, PHAssetMediaType.video.rawValue)
// For Images and Videos
// fetchOptions.predicate = NSPredicate(format: "mediaType == %d || mediaType == %d", PHAssetMediaType.image.rawValue, PHAssetMediaType.video.rawValue)
// For Videos with some duration, here I’m taking it as 10 second
fetchOptions.predicate = NSPredicate(format: "mediaType = %d AND duration < 10", PHAssetMediaType.video.rawValue)
fetchOptions.fetchLimit = 50
let imagesAndVideos = PHAsset.fetchAssets(with: fetchOptions)
print(“LIST: \(imagesAndVideos)”)
return options
}
Hope this will work for you too.
Create AVPlayer Instance :
let videoURL = "your video url"
// Create an AVPlayer, passing it the local video url path
let player = AVPlayer(url: videoURL as URL)
let controller = AVPlayerViewController()
controller.player = player
present(controller, animated: true) {
player.play()
}
Do not forgot to import AVKit and AVFoundation.
Also try to make AvPlayer instance globally.
I want to perform an operation on a video in the iphone camera roll, and I require an absolute URI as I will be using ffmpeg natively within my app.
Is it possible to operate on the video in place? or would I need to copy the video to a tmp dir, operate on it, and then write back to the camera roll?
I've read some docs and tutorials and answering below based on that research.
Is it possible to operate on the video in place?
Yes (By copying it to temp dir) and No (to the original location where the video is actually stored)
Take a look at the following image and quote from official docs
Using PhotoKit, you can fetch and cache assets for display and
playback, edit image and video content or manage collections of
assets such as albums, Moments, and Shared Albums.
We don't have direct access to the location where the image/video is stored instead we get raw data or representation using PHAsset and Asset objects are immutable so we can't perform operations directly on it. We would need PHAssetChangeRequest to create, delete, change the metadata for, or edit the content of a Photos asset.
would I need to copy the video to a temp dir, operate on it, and then write back to the camera roll?
Yep, that's the way to go.
If you already fetched the assets, and have the PHFetchResult object try:
var video = PHAsset() // the video to be edited
…
if video.canPerform(.content) { // check if the selected PHAsset can be edited
video.requestContentEditingInput(with: nil, completionHandler: { editingInput, _ in
let videoAsset = editingInput?.audiovisualAsset // get tracks and metadata of the video and start editing
let videoURL = (videoAsset as? AVURLAsset)?.url // This might be nil so better use videoAsset
/*
Start editing your video here
*/
guard let input = editingInput else { return }
let output = PHContentEditingOutput(contentEditingInput: input)
let outputURL = output.renderedContentURL // URL at which you write/export the edited video, it must be a .mov file
let editedVideo = NSData() // suppose your video fileName is editedVideo.mov, I used NSData since I don't know what final edited object will be.
editedVideo.write(to: outputURL, atomically: false)
PHPhotoLibrary.shared().performChanges({
let changeRequest = PHAssetChangeRequest(for: video)
changeRequest.contentEditingOutput = output
})
})
}
Or if you're using default imagePicker, we can get tmp video url using:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
let videoURL = info[UIImagePickerController.InfoKey.mediaURL] as! NSURL
print(videoURL) // file is already in tmp folder
let video = info[UIImagePickerController.InfoKey.phAsset] as! PHAsset
// implement the above code using this PHAsset
// your will still need to request photo library changes, and save the edited video and/or delete the older one
}
I implement something like this in my project I hope it will help you.
I show all the items in collection view and perform action on selection , you can also get the url of selected video
func getVideoFromCameraRoll() {
let options = PHFetchOptions()
options.sortDescriptors = [ NSSortDescriptor(key: "creationDate", ascending: false) ]
options.predicate = NSPredicate(format: "mediaType = %d", PHAssetMediaType.video.rawValue)
videos = PHAsset.fetchAssets(with: options)
videoLibraryCV.reloadData()
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return videos.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
let asset = videos!.object(at: indexPath.row)
let width: CGFloat = 150
let height: CGFloat = 150
let size = CGSize(width:width, height:height)
cell.layer.borderWidth = 0.5
cell.layer.borderColor = UIColor.lightGray.cgColor
PHImageManager.default().requestImage(for: asset, targetSize: size, contentMode: PHImageContentMode.aspectFit, options: nil)
{ (image, userInfo) -> Void in
let imageView = cell.viewWithTag(1) as! UIImageView
imageView.image = image
let labelView = cell.viewWithTag(2) as! UILabel
labelView.text = String(format: "%02d:%02d",Int((asset.duration / 60)),Int(asset.duration) % 60)
}
return cell
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
let asset = photos!.object(at: indexPath.row)
guard(asset.mediaType == PHAssetMediaType.Video)
else {
print("Not a valid video media type")
return
}
PHCachingImageManager().requestAVAssetForVideo(asset, options: nil, resultHandler: {
(asset: AVAsset ? , audioMix : AVAudioMix ? , info : [NSObject: AnyObject] ? ) in
let asset = asset as!AVURLAsset
print(asset.URL) // Here is video URL
})
}
I hope it will work for you ...:)
I'm relatively new to Swift.
I'm currently trying to grab videos stored in the photo library and display them in a collection view. After selecting a video in the collection view, I want to be able to play the video.
Right now I've written part of the function grabVideos and I have 2 questions:
How should I store these videos? Can they be stored as UIImages? A lot of the other sources I found grabbed videos from online sources and they just stored the video url
What should I do in the resultHandler? I would assume thats were I store my videos into a global array
Note: code below is in a function called getVideos()
let imgManager = PHImageManager.default()
let requestOption = PHVideoRequestOptions()
requestOption.isSynchronous = true
requestOption.deliveryMode = .highQualityFormat
let fetchOption = PHFetchOptions()
fetchOption.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
if let fetchResult:PHFetchResult = PHAsset.fetchAssets(with: .video, options: fetchOption) {
if fetchResult.count > 0 {
for i in 0...fetchResult.count {
imgManager.requestAVAsset(forVideo: fetchResult.object(at: i) as! PHAsset, options: requestOption, resultHandler: {{ (<#AVAsset?#>, <#AVAudioMix?#>, <#[AnyHashable : Any]?#>) in
<#code#>
}})
}
} else {
print("Error: No Videos Found")
}
}
First you add a variable to your ViewController var fetchResults: PHFetchResult<PHAsset>?
Then you execute the fetch in viewDidLoad for instance
let fetchOption = PHFetchOptions()
fetchOption.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
let fetchResults = PHAsset.fetchAssets(with: .video, options: fetchOption);
self.fetchResults = fetchResults
if fetchResults.count == 0 {
print("Error: No Videos Found")
return
}
In your collectionViewCell you have to add a UIImageView so that we can show thumbnail in each cell, then you just do the following in collection view data source methods
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return fetchResults?.count ?? 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "reuse", for: indexPath) as! TestCollectionViewCell
let videoAsset = fetchResults!.object(at: indexPath.item)
PHImageManager.default().requestImage(for: videoAsset, targetSize: cell.bounds.size, contentMode: .aspectFill, options: nil) { (image: UIImage?, info: [AnyHashable : Any]?) in
cell.imageView.image = image
}
return cell
}
This can be approved upon, but will be OK for the first try, specifically look at using PHCachingImageManager instead of PHImageManager, more on this in the links below:
PHCachingImageManager
How to use PHCachingImageManager
How to play a video from PHAsset you can find answered:
Swift - Playing Videos from iOS PHAsset
I'm making an app that lets you record a video, then save it to the documents directory and a Post object, along with a thumbnail image. When I record a video and tap "use video", I see my print statements that the video was saved to the documents directory, and to the post object. However when I go to my collection view, which is set to display the thumbnail images from Post, it's empty. This is the function that handles saving the video and appending the Post object:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
var posts = [Post]()
let mediaType = info[UIImagePickerControllerMediaType] as! NSString
dismiss(animated: true, completion: nil)
if mediaType == kUTTypeMovie {
var uniqueVideoID = ""
var videoURL:NSURL? = NSURL()
var uniqueID = ""
uniqueID = NSUUID().uuidString
// Get the path as URL and store the data in myVideoVarData
videoURL = info[UIImagePickerControllerMediaURL] as? URL as NSURL?
let myVideoVarData = try! Data(contentsOf: videoURL! as URL)
// Write data to temp diroctory
let tempPath = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)
let tempDocumentsDirectory: AnyObject = tempPath[0] as AnyObject
uniqueVideoID = uniqueID + "TEMPVIDEO.MOV"
let tempDataPath = tempDocumentsDirectory.appendingPathComponent(uniqueVideoID) as String
try? myVideoVarData.write(to: URL(fileURLWithPath: tempDataPath), options: [])
//Getting the time value of the movie.
let fileURL = URL(fileURLWithPath: tempDataPath)
let asset = AVAsset(url: fileURL)
let duration : CMTime = asset.duration
//Now we remove the data from the temp Document Diroctory.
do{
let fileManager = FileManager.default
try fileManager.removeItem(atPath: tempDataPath)
} catch {
//Do nothing
}
// Check to see if video is under the 18500 (:30 seconds)
if duration.value <= 18500 {
// Write the data to the Document Directory for use later on
let docPaths = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)
let documentsDirectory: AnyObject = docPaths[0] as AnyObject
uniqueVideoID = uniqueID + "VIDEO.MOV"
let docDataPath = documentsDirectory.appendingPathComponent(uniqueVideoID) as String
try? myVideoVarData.write(to: URL(fileURLWithPath: docDataPath), options: [])
print("docDataPath under picker ",docDataPath)
print("Video saved to documents directory")
//Create a thumbnail image from the video
let assetImageGenerate = AVAssetImageGenerator(asset: asset)
assetImageGenerate.appliesPreferredTrackTransform = true
let time = CMTimeMake(asset.duration.value / 3, asset.duration.timescale)
//Add thumbnail & video path to Post object
if let videoImage = try? assetImageGenerate.copyCGImage(at: time, actualTime: nil) {
let video = Post(pathToVideo: docDataPath, thumbnail: UIImage(cgImage: videoImage))
posts.append(video)
print("Video saved to Post object")
}
}else{
print("Video not saved")
}
}
}
If I set a breakpoint I can see that Post does indeed have one value after the save, so I'm not sure why the CV isn't displaying the thumbnail.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "postCell", for: indexPath) as! PostCell
cell.postImage.image = posts[indexPath.row].thumbnail
cell.postImage.contentMode = UIViewContentMode.scaleAspectFill
return cell
}
Post Object:
class Post: NSObject {
var thumbnail: UIImage!
var pathToVideo: String!
init(pathToVideo: String, thumbnail: UIImage) {
self.pathToVideo = pathToVideo
self.thumbnail = thumbnail
}
}
Can anyone see what's going wrong?
Do you have a singleton VideoFeedController? What are you trying to accomplish by doing this:
let feed = VideoFeedController()
feed.collectionView?.reloadData()
Seems to me you are reloading an instance of the VideoFeedController that is never displayed anywhere.
if your image picking function is within the VideoFeedController you could simply call
self.collectionView?.reloadData()
but if not, then you could try overriding viewDidAppear inside the VideoFeedController and reload the collectionView there, i.e. add this to the VideoFeedController
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.collectionView?.reloadData()
}
I'm recording a video with AVFoundation, and I created a preview from the video, when I finish to record a video, the video is playing in the preview, but when I save the video using:
var fileName = "\(self.tField.text!).mp4"
fileName = fileName.stringByReplacingOccurrencesOfString(" ", withString: "_")
let path = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0]
//let originVideoFile = self.filePath
let destinationVideoFile = path.URLByAppendingPathComponent(fileName)
let data = NSData(contentsOfURL: self.filePath)
//try data!.writeToURL(destinationVideoFile!, options: [])
let fileManager = NSFileManager()
fileManager.createFileAtPath((destinationVideoFile?.path)!, contents: data, attributes: nil)
The video file is created but I don't have access to this file when I want re play the video, but the camera roll can play the video.
to get the "filePath" I'm using the delegate method of AVFoundation:
func captureOutput(captureOutput: AVCaptureFileOutput!, didFinishRecordingToOutputFileAtURL outputFileURL: NSURL!, fromConnections connections: [AnyObject]!, error: NSError!) {
print("capture did finish")
print(captureOutput);
print(outputFileURL);
self.filePath = outputFileURL
performSegueWithIdentifier("previewVideo", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "previewVideo"{
let destination = segue.destinationViewController as! PreviewVC
destination.filePath = self.filePath
destination.videoDelegate = self.videoDelegate
}
}
and pass the data with "prepareForSegue"
I return a video object with the information needed, and when I want to re play a video, I using the method "didSelect" of the collectionview I create a "AVPlayer" and use the name of the file to found the path of the video, then the file exist but can't play
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
videoSingleObject = videoObjects[indexPath.item]
let path = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as String
let url = NSURL(fileURLWithPath: path)
let filePath = url.URLByAppendingPathComponent(videoSingleObject.nameOfFile)!.path!
let fileManager = NSFileManager.defaultManager()
if fileManager.fileExistsAtPath(filePath) {
print("FILE AVAILABLE")
UISaveVideoAtPathToSavedPhotosAlbum(filePath, nil, nil, nil)
print("PATH: \(filePath)")
let videoPath = NSURL(string: filePath)!
//filePathURL = videoPath
print(videoPath)
asset = AVAsset(URL: videoPath)
playerItem = AVPlayerItem(asset: asset)
print("DURATION: \(CMTimeGetSeconds(asset.duration))")
player = AVPlayer(playerItem: playerItem)
playerViewController.player = player
self.presentViewController(playerViewController, animated: true, completion: nil)
player.play()
}else {
print("THE DIRECTORY NOT EXIST")
}
}
but I use "UISaveVideoAtPathToSavedPhotosAlbum(filePath, nil, nil, nil)" to share the video in the cameraRoll, and in the cameraRoll the video is can play.
i found the error, the problem is the URL reference, i'm using a NSURL(string: filePath), i solved the problem using NSURL(fileURLwithPath: filePath)