본문 바로가기
iOS

🎥 iOS 비디오 재생, 녹화, 병합 튜토리얼 (1)

by HaningYa 2020. 8. 31.
728x90

source: https://www.raywenderlich.com/10857372-how-to-play-record-and-merge-videos-in-ios-and-swift

[출처]

 

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!

www.raywenderlich.com

목표

  • 비디오 선택 후 재생
  • 비디오 녹화 후 저장
  • 비디오 합치기

기본화면구성

  • 버튼3개
  • stackview 로 그냥 감쌈
  • vertical, horizontal center

재생할 비디오 목록에서 선택

import AVKit
import MobileCoreServices
  • AVKit: AVPlayer에 접근할 수 있음(선택된 비디오 재생)
  • MobileCoreService: kUTTypeMovie 와 같은 predefined constants 가 있다. (나중에 쓰임)
extension ViewController : UIImagePickerControllerDelegate {
    
}
extension ViewController : UINavigationControllerDelegate {
    
}
  • ImagePicker delegate 와 navigation controller delegate를 추가시킨다. 
  • Image Picker를 사용해 갤러리에 있는 비디오를 선택할 것이다.(이름은 image picker 이지만 video 도 가능)

VideoHelper class 를 만들어 준다. 

import MobileCoreServices
import UIKit

enum VideoHelper {
  static func startMediaBrowser(
    delegate: UIViewController & UINavigationControllerDelegate & UIImagePickerControllerDelegate,
    sourceType: UIImagePickerController.SourceType
  ) {
    guard UIImagePickerController.isSourceTypeAvailable(sourceType)
      else { return }

    let mediaUI = UIImagePickerController()
    mediaUI.sourceType = sourceType
    mediaUI.mediaTypes = [kUTTypeMovie as String]
    mediaUI.allowsEditing = true
    mediaUI.delegate = delegate
    delegate.present(mediaUI, animated: true, completion: nil)
  }
}

actionSelectAndPlay 버튼에 startMediaBrowser 메서드를 구현해 준다.

//
//  ViewController.swift
//  AVFoundationPractice
//
//  Created by TaeHyeong Kim on 2020/08/30.
//  Copyright © 2020 TaeHyeong Kim. All rights reserved.
//

import UIKit
import AVKit
import MobileCoreServices


class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }

    @IBAction func actionSelectAndPlay(_ sender: Any) {
        VideoHelper.startMediaBrowser(delegate: self, sourceType: .savedPhotosAlbum)
    }
    @IBAction func actionRecordAndSave(_ sender: Any) {
    }
    @IBAction func actionMerge(_ sender: Any) {
    }
}
extension ViewController : UIImagePickerControllerDelegate {
    
}
extension ViewController : UINavigationControllerDelegate {
    
}
  • 헬퍼 클래스의 startMediaBroswer는 image picker를 열며 self 에 있는 delegate를 통해 이벤트를 전달할 것 이다.

비디오 선택 완성


비디오를 선택한 다음 어떻게 처리할지 구현

UIImagePickerControllerDelegate 에 imagePickerController 함수를 구현한다.

extension ViewController : UIImagePickerControllerDelegate {
    func imagePickerController(
        _ picker: UIImagePickerController,
        didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]
    ) {
 
            // 1
            guard
                let mediaType = info[UIImagePickerController.InfoKey.mediaType] as? String,
                mediaType == (kUTTypeMovie as String),
                let url = info[UIImagePickerController.InfoKey.mediaURL] as? URL
                else { return }
            
            // 2
            dismiss(animated: true) {
                //3
                let player = AVPlayer(url: url)
                let vcPlayer = AVPlayerViewController()
                vcPlayer.player = player
                self.present(vcPlayer, animated: true, completion: nil)
            }

    }
    
}
  • 비디오 선택이 끝나면 (didFinishPickingMediaWithInfo) 미디어 타입과 파일의 url을 가지고 온다.
  • 작업이 성공하면 원래 picker 창은 닫고 (dismiss) 해당 비디오를 AVPlayer를 통해 플레이어에서 보여준다. (present)

 


카메라로 동영상 촬영하기 (에뮬에서는 sourceType .camera는 실행안됨)

record and save 버튼에 startMediaBrowser 를 구현하는데 sourceType 은 .camera로 한다.

class ViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }
    
    @IBAction func actionSelectAndPlay(_ sender: Any) {
        VideoHelper.startMediaBrowser(delegate: self, sourceType: .savedPhotosAlbum)
    }
    @IBAction func actionRecordAndSave(_ sender: Any) {
        VideoHelper.startMediaBrowser(delegate: self, sourceType: .camera)
    }
    @IBAction func actionMerge(_ sender: Any) {
        
    }
}

동영상 촬영 후 포토앨범에 저장하기

비디오를 선택하면 UIImagePickerControllerDelegate 가 호출된다. 해당 delegaate 에 아래의 코드를 작성한다.

그런데 photoLibrary 와 Camera 에 따라 다르게 처리할거니 switch 를 통해 분기처리한다.

extension ViewController : UIImagePickerControllerDelegate {
    func imagePickerController(
        _ picker: UIImagePickerController,
        didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]
    ) {
        switch picker.sourceType {
        case .photoLibrary:
            // 1
            guard
                let mediaType = info[UIImagePickerController.InfoKey.mediaType] as? String,
                mediaType == (kUTTypeMovie as String),
                let url = info[UIImagePickerController.InfoKey.mediaURL] as? URL
                else { return }
            
            // 2
            dismiss(animated: true) {
                //3
                let player = AVPlayer(url: url)
                let vcPlayer = AVPlayerViewController()
                vcPlayer.player = player
                self.present(vcPlayer, animated: true, completion: nil)
            }
        case .camera:
            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)
        default:
            break
        }
    }
    
}
  • 추가된 .camera 코드를 보면 카메라로 비디오가 녹화되고 비슷하게 해당 파일 url을 받는다. 그 파일을 PhotosAlbum에 저장한다. 
  • photoAlbum 에 저장되면 해당 비디오를 가지고 selector 에 지정된 함수를 실행한다.

동영상 포토엘범에 저장한 후 저장완료 띄우기

@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)
}

두개의 비디오와 음악 합치기

비디오와 음악은 AVAsset으로 변수에 할당할 수 있다.

합칠 음악과 비디오 2개의 변수를 추가한다.

  var firstAsset: AVAsset?
  var secondAsset: AVAsset?
  var audioAsset: AVAsset?

그리고 각각 pick 할 버튼을 만들어 준다.

  • picker 에서 비디오를 가져오는 메서드를 구현하고
  • 가져온 비디오를 구분하기 위해 처음 선택된 비디오를 firstAsset 에 두번째 비디오를 secondAsset 에 할당한다.

지금까지 전체코드

//
//  ViewController.swift
//  AVFoundationPractice
//
//  Created by TaeHyeong Kim on 2020/08/30.
//  Copyright © 2020 TaeHyeong Kim. All rights reserved.
//

import UIKit
import AVKit
import MobileCoreServices
import MediaPlayer
import Photos


class ViewController: UIViewController {
    var firstAsset: AVAsset?
    var secondAsset: AVAsset?
    var audioAsset: AVAsset?
    var loadingAssetOne = false
    var mergingVideo = false
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }
    
    @IBAction func actionSelectAndPlay(_ sender: Any) {
        VideoHelper.startMediaBrowser(delegate: self, sourceType: .savedPhotosAlbum)
        mergingVideo = false
    }
    @IBAction func actionRecordAndSave(_ sender: Any) {
        VideoHelper.startMediaBrowser(delegate: self, sourceType: .camera)
    }
    @IBAction func actionMerge(_ sender: Any) {
        
    }
    
    @IBAction func actionLoadVid1(_ sender: Any) {
        mergingVideo = true
        VideoHelper.startMediaBrowser(delegate: self, sourceType: .savedPhotosAlbum)
        
    }
    @IBAction func actionLoadVid2(_ sender: Any) {
        mergingVideo = true
        VideoHelper.startMediaBrowser(delegate: self, sourceType: .savedPhotosAlbum)
        
    }
    @IBAction func actionLoadMusic(_ sender: Any) {
        let mediaPickerController = MPMediaPickerController(mediaTypes: .any)
        mediaPickerController.delegate = self
        mediaPickerController.prompt = "Select Audio"
        present(mediaPickerController, animated: true, completion: nil)
    }
    @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)
    }
}
extension ViewController : UIImagePickerControllerDelegate {
    func imagePickerController(
        _ picker: UIImagePickerController,
        didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]
    ) {
        switch picker.sourceType {
        case .savedPhotosAlbum:
            if mergingVideo {
                dismiss(animated: true, completion: nil)
                
                guard let mediaType = info[UIImagePickerController.InfoKey.mediaType] as? String,
                    mediaType == (kUTTypeMovie as String),
                    let url = info[UIImagePickerController.InfoKey.mediaURL] as? URL
                    else { return }
                
                let avAsset = AVAsset(url: url)
                var message = ""
                if loadingAssetOne {
                    message = "Video one loaded"
                    firstAsset = avAsset
                } else {
                    message = "Video two loaded"
                    secondAsset = avAsset
                }
                let alert = UIAlertController(
                    title: "Asset Loaded",
                    message: message,
                    preferredStyle: .alert)
                alert.addAction(UIAlertAction(
                    title: "OK",
                    style: UIAlertAction.Style.cancel,
                    handler: nil))
                present(alert, animated: true, completion: nil)
            }else{
                // 1
                guard
                    let mediaType = info[UIImagePickerController.InfoKey.mediaType] as? String,
                    mediaType == (kUTTypeMovie as String),
                    let url = info[UIImagePickerController.InfoKey.mediaURL] as? URL
                    else { return }
                
                // 2
                dismiss(animated: true) {
                    //3
                    let player = AVPlayer(url: url)
                    let vcPlayer = AVPlayerViewController()
                    vcPlayer.player = player
                    self.present(vcPlayer, animated: true, completion: nil)
                }
            }
            
        case .camera:
            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)
        default:
            break
        }
    }
}
extension ViewController : UINavigationControllerDelegate {
    
}

 


오디오 파일 선택하는 코드 구현

import MediaPlayer

 @IBAction func actionLoadMusic(_ sender: Any) {
        let mediaPickerController = MPMediaPickerController(mediaTypes: .any)
        mediaPickerController.delegate = self
        mediaPickerController.prompt = "Select Audio"
        present(mediaPickerController, animated: true, completion: nil)
    }
extension ViewController: MPMediaPickerControllerDelegate {
    
}

오디오 파일 선택 후 동작 delegate 에 구현

extension ViewController: MPMediaPickerControllerDelegate {
    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)
    }
    
}

이제 비디오 2개와 오디오 파일 1개 선택해서 불러오는 것 까지 했다.
다음 포스팅에서 합쳐보는걸로

 

🎥 iOS 비디오 재생, 녹화, 병합 튜토리얼 (2)

이전 포스팅 🎥 iOS 비디오 재생, 녹화, 병합 튜토리얼 (1) [출처] 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 tutori..

haningya.tistory.com

 

728x90

댓글