How to Play, Record and Merge Videos in iOS and Swift

Learn the basics of working with videos on iOS with AV Foundation in this tutorial. You’ll play, record and even do some light video editing! By Owen L Brown.

4.3 (20) · 3 Reviews

Download materials
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

Saving Video

Back in RecordVideoViewController.swift, add the following method to the UIImagePickerControllerDelegate extension:

func imagePickerController(
  _ picker: UIImagePickerController,
  didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]
) {
  dismiss(animated: true, completion: nil)
  
  guard
    let mediaType = info[UIImagePickerController.InfoKey.mediaType] as? String,
    mediaType == (kUTTypeMovie as String),
    // 1
    let url = info[UIImagePickerController.InfoKey.mediaURL] as? URL,
    // 2
    UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(url.path)
    else { return }
  
  // 3
  UISaveVideoAtPathToSavedPhotosAlbum(
    url.path,
    self,
    #selector(video(_:didFinishSavingWithError:contextInfo:)),
    nil)
}

Don’t worry about the error — you’ll take care of that shortly.

  1. As before, the delegate method gives you a URL pointing to the video.
  2. Verify that the app can save the file to the device’s photo album.
  3. If it can, save it.

UISaveVideoAtPathToSavedPhotosAlbum is the function provided by the SDK to save videos to the device’s photo album. You pass it the path to the video you want to save as well as a target and action to call back, which will inform you of the status of the save operation.

Next, add the implementation of the callback to the main class definition:

@objc func video(
  _ videoPath: String,
  didFinishSavingWithError error: Error?,
  contextInfo info: AnyObject
) {
  let title = (error == nil) ? "Success" : "Error"
  let message = (error == nil) ? "Video was saved" : "Video failed to save"

  let alert = UIAlertController(
    title: title,
    message: message,
    preferredStyle: .alert)
  alert.addAction(UIAlertAction(
    title: "OK",
    style: UIAlertAction.Style.cancel,
    handler: nil))
  present(alert, animated: true, completion: nil)
}

The callback method simply displays an alert to the user, announcing whether the video file was saved or not, based on the error status.

Build and run. Record a video and select Use Video when you’re done recording. If you’re asked for permission to save to your video library, tap OK. When the Video was saved alert pops up, you just successfully saved your video to the photo library!

Pop-up with message: Video was saved

Now that you can play videos and record videos, it’s time to take the next step and try some light video editing.

Merging Videos

The final piece of functionality for the app is to do a little editing. Your user will select two videos and a song from the music library, and the app will combine the two videos and mix in the music.

The project already has a starter implementation in MergeVideoViewController.swift, with the code similar to the code you wrote to play a video. The big difference is when merging, the user must select two videos. That part is already set up, so the user can make two selections that will be stored in firstAsset and secondAsset.

The next step is to add the functionality to select the audio file.

Selecting the Audio File

UIImagePickerController provides functionality to select only video and images from the media library. To select audio files from your music library, you must use MPMediaPickerController. It works essentially the same as UIImagePickerController, but instead of images and video, it accesses audio files in the media library.

Open MergeVideoViewController.swift and add the following code to loadAudio(_:):

let mediaPickerController = MPMediaPickerController(mediaTypes: .any)
mediaPickerController.delegate = self
mediaPickerController.prompt = "Select Audio"
present(mediaPickerController, animated: true, completion: nil)

The code above creates a new MPMediaPickerController instance and displays it as a modal view controller.

Build and run. Now, tap Merge Video, then Load Audio to access the audio library on your device.

Of course, you’ll need some audio files on your device. Otherwise, the list will be empty. The songs will also have to be physically present on the device, so make sure you’re not trying to load a song from the cloud.

The audio library

Select a song from the list and you’ll notice that nothing happens. That’s right! MPMediaPickerController needs delegate methods!

To implement them, find the MPMediaPickerControllerDelegate extension at the bottom of the file and add the following two methods to it:

func mediaPicker(
  _ mediaPicker: MPMediaPickerController,
  didPickMediaItems mediaItemCollection: MPMediaItemCollection
) {
  // 1
  dismiss(animated: true) {
    // 2
    let selectedSongs = mediaItemCollection.items
    guard let song = selectedSongs.first else { return }
    
    // 3
    let title: String
    let message: String
    if let url = song.value(forProperty: MPMediaItemPropertyAssetURL) as? URL {
      self.audioAsset = AVAsset(url: url)
      title = "Asset Loaded"
      message = "Audio Loaded"
    } else {
      self.audioAsset = nil
      title = "Asset Not Available"
      message = "Audio Not Loaded"
    }

    // 4
    let alert = UIAlertController(
      title: title,
      message: message,
      preferredStyle: .alert)
    alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil))
    self.present(alert, animated: true, completion: nil)
  }
}

func mediaPickerDidCancel(_ mediaPicker: MPMediaPickerController) {
  // 5
  dismiss(animated: true, completion: nil)
}

The code above is similar to the delegate methods for UIImagePickerController. Here’s what it does:

  1. Dismiss the picker just like you did before.
  2. Find the selected songs and, from that, the first one in the case that multiple are selected.
  3. Obtain the URL to the media asset that backs the song. Then make an AVAsset pointing to the song that was chosen.
  4. Finally, for mediaPicker(_:didPickMediaItems:), show an alert to indicate if the asset was successfully loaded or not.
  5. In the case the media picker was canceled, simply dismiss the view controller.

Build and run, then go to the Merge Videos screen. Select an audio file and you’ll see the Audio Loaded message.

Pop-up with message: Audio Loaded

You now have all your assets loading correctly so it’s time to merge the various media files into one file. But before you get into that code, you need to do a bit of setup.

Merging Completion Handler

You will shortly write the code to merge your assets. This will need a completion handler that saves the final video to the photo album. You’ll add this first.

Add the following import statement at the top of the MergeVideoViewController.swift file:

import Photos

Then, add the method below to MergeVideoViewController:

func exportDidFinish(_ session: AVAssetExportSession) {
  // 1
  activityMonitor.stopAnimating()
  firstAsset = nil
  secondAsset = nil
  audioAsset = nil

  // 2
  guard
    session.status == AVAssetExportSession.Status.completed,
    let outputURL = session.outputURL
    else { return }

  // 3
  let saveVideoToPhotos = {
    // 4
    let changes: () -> Void = {
      PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: outputURL)
    }
    PHPhotoLibrary.shared().performChanges(changes) { saved, error in
      DispatchQueue.main.async {
        let success = saved && (error == nil)
        let title = success ? "Success" : "Error"
        let message = success ? "Video saved" : "Failed to save video"

        let alert = UIAlertController(
          title: title,
          message: message,
          preferredStyle: .alert)
        alert.addAction(UIAlertAction(
          title: "OK",
          style: UIAlertAction.Style.cancel,
          handler: nil))
        self.present(alert, animated: true, completion: nil)
      }
    }
  }
    
  // 5
  if PHPhotoLibrary.authorizationStatus() != .authorized {
    PHPhotoLibrary.requestAuthorization { status in
      if status == .authorized {
        saveVideoToPhotos()
      }
    }
  } else {
    saveVideoToPhotos()
  }
}

Here’s what that code does:

  1. There’s a spinner that will animate when the assets are being processed. This stops the spinner and then clears the assets ready to select new ones.
  2. Ensure that the processing is complete and there is a URL of the resulting video.
  3. Create a closure that…
  4. Tells the photo library to make a “create request” from the resulting video before showing an alert to indicate if this succeeds or fails.
  5. Check if there is permission to access the photo library. If there is not permission, then ask for it before running the closure that saves the video. Otherwise, simply run the closure immediately as permission is already granted.

Now, you’ll add some code to merge(_:). Because there’s a lot of code, you’ll complete this in steps.