Load video from PHPhotoPicker without photo library permissions?

The sample code for PHPhotoPicker doesn't include an example of how to load a video. I got it working by using the PHPickerResult’s assetIdentifier and PHAsset.fetchAssets(withLocalIdentifiers:options:), but that requires additional photo library permissions. How can I load a movie directly from the PHPickerResult?

Replies

You can use an appropriate PHPickerFilter option to make sure videos are included, which it sounds like you've already done.

Once you get the item provider back, you can check if a video is available with:

Code Block
itemProvider.hasItemConformingToTypeIdentifier("public.movie")


And then you can get access to the URL to the video file on disk with:

Code Block
itemProvider.loadItem(forTypeIdentifier: "public.movie", options: nil) { /* do stuff */ }


That should get you access to the file without needing photo library access. Hope this helps!

I'm doing that (using UTType.movie.identifier instead of the literal "public.movie"), and then loading the resulting URL into an AVPlayer, but I get an error message and the video doesn't play.

Here's my code:

Code Block swift
import AVKit
import PhotosUI
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func selectMovieTapped(_ sender: UIButton) {
var config = PHPickerConfiguration()
config.filter = .videos
let picker = PHPickerViewController(configuration: config)
picker.delegate = self
present(picker, animated: true, completion: nil)
}
}
extension ViewController: PHPickerViewControllerDelegate {
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
picker.dismiss(animated: true, completion: nil)
guard let provider = results.first?.itemProvider else { return }
if provider.hasItemConformingToTypeIdentifier(UTType.movie.identifier) {
provider.loadItem(forTypeIdentifier: UTType.movie.identifier, options: [:]) { [self] (videoURL, error) in
print("resullt:", videoURL, error)
DispatchQueue.main.async {
if let url = videoURL as? URL {
let player = AVPlayer(url: url)
let playerVC = AVPlayerViewController()
playerVC.player = player
present(playerVC, animated: true, completion: nil)
}
}
}
}
}
}


It prints the following when I pick a video on the simulator:

Code Block text
resullt: Optional(file:///Users/zev/Library/Developer/CoreSimulator/Devices/C5BEBCF2-D16A-41F7-B788-6CC208B02D4C/data/Containers/Shared/AppGroup/D7862E29-F13B-42A8-A74B-721676090D8D/File%20Provider%20Storage/47F700FF-7501-49A0-B84B-23E701A758F9.mov) nil
2020-07-01 07:56:34.992199-0400 DeflickerPOC[20292:373394] [] [07:56:34.992] FigFileForkOpenMainByCFURL signalled err=2 (errno) (open failed) at /Library/Caches/com.apple.xbs/Sources/EmbeddedCoreMediaFramework_Sim/EmbeddedCoreMedia-2729.5.1.3/Sources/Platform/Darwin/DarwinFile.c:564


And the player VC presents, but is not playable. It shows a ▶️ with a 🚫 through it (roughly; I can't attach or link to a screenshot on this forum, apparently).
I'm also trying to figure this out. Also, how does getting a url to the video help with downloading it if it is on the cloud and not on your device?

Is the new photo picker suitable for apps that allow selecting multiple photos/videos and putting them together into a single project? This requires being able to download multiple photos/videos (showing progress) and then manipulating the photos as UIImages and the videos as AVAssets.

I have a bunch of photo/video apps and am struggling to see my way through all this without just asking the user to please give access to all photos.
You are required to copy files to a location your app controls before returning from the load completion handler. The system can delete the temporary file when the completion handler returns.

Since you are working with a file representation of the item, I'd also recommend to switch to loadFileRepresentation(forTypeIdentifier:completionHandler:) which guarantees a URL as type.
The documentation of that method also states the copy requirement more explicitly:

This method writes a copy of the file’s data to a temporary file, which the system deletes when the completion handler returns.

Note that we currently have some issues preventing some assets from being returned (64630315). It will be fixed in a future release.
Update on the iOS 14 Beta 6:

On my side, this:
Code Block swift
itemProvider.loadItem(forTypeIdentifier: "public.movie", options: nil) { /* do stuff */ }

Gives me a URL pointing to a file that does not exist. So until now, it seems impossible to load a video from the new PHPickerViewController.

Is this a known issue?

Please switch to loadFileRepresentation(forTypeIdentifier:completionHandler:) which guarantees a URL as type.
Are we supposed to copy these files locally? Because it doesn't loads in AVPlayer.
I'm seeing some weird behavior with NSItemProvider and PHPickerViewController.
For some reason loadItem fails until I've used loadFileRepresentation on that same video.

Code Block
itemProvider.loadItem(forTypeIdentifier: "public.movie", options: nil) { url, error in
guard let url = url as? URL else {
return
}
print(FileManager.default.fileExists(atPath: url.path))
}

This prints false.

Code Block
itemProvider.loadFileRepresentation(forTypeIdentifier: "public.movie") { url, error in
print(FileManager.default.fileExists(atPath: url.path))
}

This prints true.

Now if I change the code to use loadItem and select the video that I just used loadFileRepresentation on, it works correctly.

I would happily use loadFileRepresentation all the time, but it loads extremely slowly. ~20 seconds for a minute long video, while loadItem takes about .1 seconds for the same video.

What's going on here?

Now if I change the code to use loadItem and select the video that I just used loadFileRepresentation on, it works correctly.
I would happily use loadFileRepresentation all the time, but it loads extremely slowly. ~20 seconds for a minute long video, while loadItem takes about .1 seconds for the same video.
What's going on here?

  1. You should always use itemProvider.loadFileRepresentation because itemProvider.loadItem is not designed for this use case.

  2. It's likely that the picker is transcoding the video for you on the fly. You can set preferredAssetRepresentationMode to .current to avoid transcoding if your app can handle arbitrary video formats (HEVC, H264, etc).

Please refer to this thread for more information.