In this article, we will learn how to record or select a video file using a video picker controller and AVPlayer class, written entirely in Swift 5.
Let’s pick some videos!
First of all you will need to add some information to your Info.plist file, because you want to access some personal data. You need to know: privacy is very important.
1 2 3 4 5 6 7 8 9 | <key>NSCameraUsageDescription</key> <string>This app wants to take pictures &amp; videos.</string> <key>NSPhotoLibraryUsageDescription</key> <string>This app wants to use your picture &amp; video library.</string> <key>NSMicrophoneUsageDescription</key> <string>This app wants to record sound.</string> <key>NSPhotoLibraryAddUsageDescription</key> <string>This app wants to save pictures &amp; videos to your library.</string> |
Since we are not going to record silent video we also have to add Privacy – Microphone Usage Description.
Our VideoPicker class will be 90% the same as the ImagePicker class. You can create an abstract class, whatever, I’ll show you the final code, then we can talk about the difference.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 | import UIKit public protocol VideoPickerDelegate: class { func didSelect(url: URL?) } open class VideoPicker: NSObject { private let pickerController: UIImagePickerController private weak var presentationController: UIViewController? private weak var delegate: VideoPickerDelegate? public init(presentationController: UIViewController, delegate: VideoPickerDelegate) { self.pickerController = UIImagePickerController() super.init() self.presentationController = presentationController self.delegate = delegate self.pickerController.delegate = self self.pickerController.allowsEditing = true self.pickerController.mediaTypes = ["public.movie"] self.pickerController.videoQuality = .typeHigh } private func action(for type: UIImagePickerController.SourceType, title: String) -> UIAlertAction? { guard UIImagePickerController.isSourceTypeAvailable(type) else { return nil } return UIAlertAction(title: title, style: .default) { [unowned self] _ in self.pickerController.sourceType = type self.presentationController?.present(self.pickerController, animated: true) } } public func present(from sourceView: UIView) { let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) if let action = self.action(for: .camera, title: "Take video") { alertController.addAction(action) } if let action = self.action(for: .savedPhotosAlbum, title: "Camera roll") { alertController.addAction(action) } if let action = self.action(for: .photoLibrary, title: "Video library") { alertController.addAction(action) } alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) if UIDevice.current.userInterfaceIdiom == .pad { alertController.popoverPresentationController?.sourceView = sourceView alertController.popoverPresentationController?.sourceRect = sourceView.bounds alertController.popoverPresentationController?.permittedArrowDirections = [.down, .up] } self.presentationController?.present(alertController, animated: true) } private func pickerController(_ controller: UIImagePickerController, didSelect url: URL?) { controller.dismiss(animated: true, completion: nil) self.delegate?.didSelect(url: url) } } extension VideoPicker: UIImagePickerControllerDelegate { public func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { self.pickerController(picker, didSelect: nil) } public func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) { guard let url = info[.mediaURL] as? URL else { return self.pickerController(picker, didSelect: nil) } // //uncomment this if you want to save the video file to the media library // if UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(url.path) { // UISaveVideoAtPathToSavedPhotosAlbum(url.path, self, nil, nil) // } self.pickerController(picker, didSelect: url) } } extension VideoPicker: UINavigationControllerDelegate { } |
Those are just a few minor changes. The first is the mediaTypes attribute, you can use the “public.movie” value this time. Also, you should set videoQuality attribute on pickerController, because 4k is always better than 320.
Delegates are the last thing to change a bit. After finishing selecting the job you can get .mediaURL, which is a URL to get the multimedia file (also called the captured / selected video file). If a new file is recorded you can also save it to media library, that’s just two extra lines of code.
Playing video files using AVPlayer & UIView
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 | import UIKit import AVFoundation open class VideoView: UIView { public enum Repeat { case once case loop } override open class var layerClass: AnyClass { return AVPlayerLayer.self } private var playerLayer: AVPlayerLayer { return self.layer as! AVPlayerLayer } public var player: AVPlayer? { get { self.playerLayer.player } set { self.playerLayer.player = newValue } } open override var contentMode: UIView.ContentMode { didSet { switch self.contentMode { case .scaleAspectFit: self.playerLayer.videoGravity = .resizeAspect case .scaleAspectFill: self.playerLayer.videoGravity = .resizeAspectFill default: self.playerLayer.videoGravity = .resize } } } public var `repeat`: Repeat = .once public var url: URL? { didSet { guard let url = self.url else { self.teardown() return } self.setup(url: url) } } @available(*, unavailable) override init(frame: CGRect) { super.init(frame: frame) self.initialize() } @available(*, unavailable) public required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) self.initialize() } public init() { super.init(frame: .zero) self.translatesAutoresizingMaskIntoConstraints = false self.initialize() } open func initialize() { } deinit { self.teardown() } private func setup(url: URL) { self.player = AVPlayer(playerItem: AVPlayerItem(url: url)) self.player?.currentItem?.addObserver(self, forKeyPath: "status", options: [.old, .new], context: nil) self.player?.addObserver(self, forKeyPath: "rate", options: [.old, .new], context: nil) NotificationCenter.default.addObserver(self, selector: #selector(self.itemDidPlayToEndTime(_:)), name: .AVPlayerItemDidPlayToEndTime, object: self.player?.currentItem) NotificationCenter.default.addObserver(self, selector: #selector(self.itemFailedToPlayToEndTime(_:)), name: .AVPlayerItemFailedToPlayToEndTime, object: self.player?.currentItem) } private func teardown() { self.player?.pause() self.player?.currentItem?.removeObserver(self, forKeyPath: "status") self.player?.removeObserver(self, forKeyPath: "rate") NotificationCenter.default.removeObserver(self, name: .AVPlayerItemDidPlayToEndTime, object: self.player?.currentItem) NotificationCenter.default.removeObserver(self, name: .AVPlayerItemFailedToPlayToEndTime, object: self.player?.currentItem) self.player = nil } @objc func itemDidPlayToEndTime(_ notification: NSNotification) { guard self.repeat == .loop else { return } self.player?.seek(to: .zero) self.player?.play() } @objc func itemFailedToPlayToEndTime(_ notification: NSNotification) { self.teardown() } open override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { if keyPath == "status", let status = self.player?.currentItem?.status, status == .failed { self.teardown() } if keyPath == "rate", let player = self.player, player.rate == 0, let item = player.currentItem, !item.isPlaybackBufferEmpty, CMTimeGetSeconds(item.duration) != CMTimeGetSeconds(player.currentTime()) { self.player?.play() } } } |
This is a demo repo you can have a more intuitive look at The.Swift.Dev tutorials repository .
External sources
- Picking images with UIImagePickerController in Swift 5
- SwiftVideoPlayer
- AVPlayer stops playing and doesn’t resume again
- How to detect AVPlayer actually started to play in swift
Conclusion
Thank you for your interest in this article, which has been translated according to the article of the same name by Tibor Bödecs.