Home

Playing Videos from Photo Library on iOS
Swift
iOS
PhotoKit

Intro

I mostly write iOS code these days, and was exploring some API's to learn and play around. Though generally iOS API's are very clean and easy to use, this one had me spend a few hours to figure out and I realized that there is not a good comprehensive guide on how to do it. That is selecting a local video from the photo library and displaying it in the app.

Seems pretty simple, but has some nuanced points.

Selecting Video Asset

First, we need to fetch the asset from the photos library. iOS already gives us an interface to complete this operation via PHPickerViewController. Simply show this view controller following a user action.

var configuration = PHPickerConfiguration(photoLibrary: .shared())
configuration.filter PHPickerFilter.any(of: [ .videos]) // Only include videos.
let picker = PHPickerViewController(configuration: configuration)
picker.delegate = self // Make sure to assign a delegate.
present(picker, animated: true)

Note that assigning delegate requires the entity to conform to PHPickerViewControllerDelegate. Conforming to this protocol is the second part of importing the video.

func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
  // If no assets are selected, dismiss the view controller.
  if (results.count < 1) {
    picker.dismiss(animated: true)
    return
  }
  let selectedAsset = results[0]
  loadVideoAsset(asset: selectedAsset)
  picker.dismiss(animated: true)
}

Seems pretty straightforward, right. Well, wait until we deep dive into loadVideoAsset method. ๐Ÿ™‚

Loading Video Asset

PHPickerViewController gives us a PHPickerResult, which is a type that represents the library asset. Since videos are large files, the API uses this meta type to make working with large files easier.

Next up, we're going to use PHPickerResult.itemProvider to load the contents of this video into our view.

func loadVideoAsset(asset: PHPickerResult) {
  let itemProvider = asset.itemProvider
  itemProvider.loadFileRepresentation(forTypeIdentifier: UTType.movie.identifier) { url, error in
      guard let videoURL = url else { return }
      // Copy video to a temporary location.
      let videoExtension = videoURL.pathExtension
      let fileManager = FileManager.default
      let destination = fileManager.temporaryDirectory.appendingPathComponent("imported.\(videoExtension)")
      let fileExists = fileManager.fileExists(atPath: destination.path())
      if (fileExists){
          try! fileManager.removeItem(at: destination)
      }
      try! fileManager.copyItem(at: videoURL, to: destination)
      // Play the video.
      DispatchQueue.main.async {
          self.playVideo(url: destination)
      }
  }
}

The most confusing part of this entire tutorial lies in this method if you ask me. And to realize it, one must read the following part of the documentation for loadFileRepresentation(forTypeIdentifier:completionHandler:) very carefully.

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

So this is why the video should be stored in a temporary location to be used in the app before the completion handler returns. And this is where the file manager comes in handy. See how in lines 6-13 we are copying the file to a temporary location.

Playing the video

Last step is to use the AVPlayer to play the video in a view.

func playVideo(url: URL){
  let playerItem = AVPlayerItem(url: url)
  let player = AVPlayer(playerItem: playerItem)
  videoViewController.player = player
  player.play()
}

This step assumes you've set up AVPlayerViewController on a view, or you can also use it display the video on a full screen experience.

And ta da, your video should be playing, hopefully! ๐Ÿ˜