포스트

TikTok Clone (9)

Save 버튼 구현

새로운 VC를 만들고 코드는 다음과 같다

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
class PreviewCapturedViewController: UIViewController {
    
    var currentlyPlayingVideoClip: VideoClips
    var recordedClips: [VideoClips]
    var viewWillDenitRestartVideoSession: (() -> Void)?
    
    deinit {
        print("PreviewCaptureVideoVC was deinited")
        (viewWillDenitRestartVideoSession)?()
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
    }
    
    init?(coder: NSCoder, recordedClips: [VideoClips]) {
        self.currentlyPlayingVideoClip = recordedClips.first!
        self.recordedClips = recordedClips
        super.init(coder: coder)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
}

init?(coder: NSCoder, recordedClips: [VideoClips])

녹화된 클립 배열을 인수로 받아 현재 재생 중인 클립을 배열의 첫 번째 요소로 설정하고, recordedClips 속성에 할당한다.

deinit을 통해 메모리에서 해제할때 viewWillDenitRestartVideoSession클로저를 호출

다시 CreatePostVC로 가서

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@IBAction func saveButtonDidTapped(_ sender: Any) {
        let previewVC = UIStoryboard(name: "Main", bundle: .main).instantiateViewController(identifier: "PreviewCapturedViewController", creator: { coder -> PreviewCapturedViewController? in
            PreviewCapturedViewController(coder: coder, recordedClips: self.recordedClips)
        })
        previewVC.viewWillDenitRestartVideoSession = { [weak self ] in
            guard let self = self else { return }
            if self.setupCaptureSession() {
                DispatchQueue.global(qos: .background).async {
                    self.captureSession.startRunning()
                }
            }
        }
        navigationController?.pushViewController(previewVC, animated: true)
    }

버튼과 관련된 기능을 구현

여기서 조금 색다른 점이라면 기본적으로 인스턴스 화를 하는방식과는 다르다.

creator를 사용 했는데, creator 클로저를 통해 PreviewCapturedViewController의 초기화 메서드인 init(coder:recordedClips:)를 호출하면서 인스턴스화 하게 된다.

이후 인스턴스화를 한 상태에서 viewWillDenitRestartVideoSession 의 클로저를 정의

PreviewCapturedViewController가 deinit될 때 호출되는 녀석이다.

captureSession.startRunning()을 호출하여 캡처 세션을 시작한다.

비디오 미리보기 구현

우선 previewVC에 uiview, uiimageview를 추가해 주었고,

3개의 변수가 추가되었다.

1
2
3
4
5
6
7
var player: AVPlayer = AVPlayer()
var playerLayer: AVPlayerLayer = AVPlayerLayer()
var urlsForVids: [URL] = [] {
        didSet {
            print("outputURLunwrapped:", urlsForVids)
        }
    }
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
     override func viewDidLoad() {
        super.viewDidLoad()
        
        handleStartPlayingFirstClip()
        hideStatusBar = true
        
        print("\(recordedClips.count)")
        recordedClips.forEach { clip in
            urlsForVids.append(clip.videoUrl)
        }
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        self.tabBarController?.tabBar.isHidden = true
        navigationController?.setNavigationBarHidden(true, animated: animated)
        player.play()
        hideStatusBar = true
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        self.tabBarController?.tabBar.isHidden = false
        navigationController?.setNavigationBarHidden(false, animated: animated)
        player.pause()
    }

VC 생명주기를 고려하여 작성이 되었고 뭐 딱히 없다.

1
2
3
4
5
6
7
    func handleStartPlayingFirstClip() {
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
            guard let firstClip = self.recordedClips.first else { return }
            self.currentlyPlayingVideoClip = firstClip
            self.setupPlayerView(with: firstClip)
        }
    }

첫번째 클립에 대해 재생하는 메서드

1
2
3
4
5
6
7
8
9
10
11
12
13
    func setupPlayerView(with videoClip: VideoClips) {
        let player = AVPlayer(url: videoClip.videoUrl)
        let playerLayer = AVPlayerLayer(player: player)
        self.player = player
        self.playerLayer = playerLayer
        playerLayer.frame = thumbnailImageView.frame
        self.player = player
        self.playerLayer = playerLayer
        thumbnailImageView.layer.insertSublayer(playerLayer, at: 3)
        player.play()
        NotificationCenter.default.addObserver(self, selector: #selector(avPlayerItemDidPlayToEndTime(notification:)), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: player.currentItem)
        handleMirrorPlayer(cameraPosition: videoClip.cameraPosition)
    }

뭐 여기부분도 크게 언급할건 없을것같고

플레이어와 플레이어레이어를 설정하고, 레이어 프레임에 썸네일 이미지 프레임을 넣어준다.

thumbnailImageView.layer.insertSublayer(playerLayer, at: 3)는 4번째 레이어에 추가를 한다는 것.

1
2
3
4
    func removePeriodicTimeobserver() {
        player.replaceCurrentItem(with: nil)
        playerLayer.removeFromSuperlayer()
    }

재생중인 플레이어를 초기화.

그리고 레이어를 제거함으로써 비디오 제거

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
    @objc func avPlayerItemDidPlayToEndTime(notification: Notification) {
        if let currentIndex = recordedClips.firstIndex(of: currentlyPlayingVideoClip) {
            let nextIndex = currentIndex + 1
            if nextIndex > recordedClips.count - 1 {
                removePeriodicTimeobserver()
                guard let firstClip = recordedClips.first else { return }
                setupPlayerView(with: firstClip)
                currentlyPlayingVideoClip = firstClip
            } else {
                for (index, clip) in recordedClips.enumerated() {
                    if index == nextIndex {
                        removePeriodicTimeobserver()
                        setupPlayerView(with: clip)
                        currentlyPlayingVideoClip = clip
                    }
                }
            }
        }
            
    }

현재 비디오가 재생이 끝났을때 다음 비디오를 재생하게 하는 로직이다.

마지막 비디오 클립 재생이 끝나면 다시 첫번째 영상을 재생하도록 한다.

removePeriodicTimeobserver를 호출하여 이전 클립의 플레이어 설정을 제거하고, setupPlayerView(with:)를 호출하여 새로운 클립을 설정한다.

1
2
3
4
5
6
7
    func handleMirrorPlayer(cameraPosition: AVCaptureDevice.Position) {
        if cameraPosition == .front {
            thumbnailImageView.transform = CGAffineTransform(scaleX: -1, y: -1)
        } else {
            thumbnailImageView.transform = .identity
        }
    }

카메라의 전후에 따라 이미지 뷰를 반전시킨다.

확인 완료.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.