iOS 앱 개발 부트캠프/TIL

TIL 18일차 - 간단한 비디오 앱 도전2

iosstudyletsgo 2024. 10. 14. 18:17

저번에 이어서 이번엔 비디오 플레이어를 커스텀 해보기로 했다.

비디오 플레이어의 커스텀에는 재생/일시정지 버튼 커스텀, 되감기/빨리감기 기능 커스텀, 비디오 재생 완료 이벤트 처리, 비디오 진행 상황 표시, 비디오 재생 품질 선택, 자막 추가, 제스처 추가 등 다양한 기능이 있다.

이 중에서 처음으로 해본 것은 비디오 재생 완료 이벤트 처리이다. 왜냐하면 재생이 완료 되었을때 처음으로 돌아가 무한 반복을 만든다던가,  다음영상을 재생한다거나 다양한 기능을 추가시킬 수 있을 것 같았기 때문이다.


영상 반복을 해보자

처음엔 간단하게 재생 중인 영상이 끝나면 처음으로 돌아가 다시 재생하는 이벤트 처리를 해보면서 이벤트 처리에 대한 감을 익혀보려고 한다.

1.  알림(Notification) 등록하기

영상을 재생해주던 AVPlayer는 비디오가 끝까지 재생되면 AVPlayerItemDidPlayToEndTime 이라는 알림이 발생한다. 따라서 이 알림을 감지하는 옵저버를 등록해 해당 이벤트를 감지하면 다음 동작을 실행할 메서드를 불러오게 만들 것이다.

NotificationCenter.default.addObserver(self, 
					selector: #selector(videoDidEnd), 
					name: .AVPlayerItemDidPlayToEndTime, 
					object: player?.currentItem)

viewDidLoad 메서드 안에 다음과 같은 코드를 추가하였다.

  • self는 옵저버를 등록하는 대상으로 여기선 viewController가 옵저버이다.
  • selector는 이벤트가 발생했을 때 호출할 메서드를 지정하는 것으로, 이후 작성할 videoDidEnd 메서드를 호출하기 위한 부분이다.
  • name은 감지하려는 이벤트로, 여기선 비디오 재생이 끝났을 때 발생하는 알림인 AVPlayerItemDidPlayToEndTime을 감지하도록 지정한 것이다.
  • object는 특정 객체와 연관된 이벤트만 감지하도록 지정하는 부분으로 여기선 currentItem, 즉 현재 재생 중인 비디오 아이템을 감지 대상으로 지정한 것이다.

따라서 이 부분은 NotificationCenter에 addObserver를 통해 현재 재생중인 비디오 아이템(currentItem)에서  비디오가 끝까지 재생됐을때 발생하는 이벤트(AVPlayerItemDidPlayToEndTime 알림)을 감지하는 옵저버(self)를 등록해, 알림이 감지되면 메서드(videoDidEnd)를 호출하는 코드이다.

2.  호출 될 메서드 정의하기

재생 종료 이벤트를 감지하는 옵저버를 추가했으니 다음으론 이벤트 처리를 하는 메서드를 만들어야 한다. 이번엔 단순 반복을 하기로 했으니 재생이 종료되면 처음으로 돌아가 재생하는 부분을 추가하면 될 것이다. 다음과 같이 메서드를 작성하였다.

    @objc func videoDidEnd(notification: NSNotification){
        print("비디오가 끝났습니다.")
        player?.seek(to: CMTime.zero)
        player?.play()
    }

메서드 정의에 앞서 @objc라는 어노테이션을 붙였다. NotificationCenter가 swift이전에 쓰이던 Object-C에서 작성된 NSNotificationCenter 클래스를 기반으로 하기 때문에 작성중인 swift 코드와의 호환을 위해 붙인 것이다.

  • print() 부분은 비디오 재생이 끝났을 때 콘솔에 출력하는 것으로 디버깅 목적으로 작성하였다.
  • seek(to: CMTime.zero) 부분은 비디오를  처음 위치로 이동시키는 역할을 한다. CMTime.zero가 비디오의 시작 지점을 나타낸다.
  • play() 부분은 비디오를 재생시키는 것으로, seek(to:) 부분을 통해 시작 지점으로 이동시킨 뒤 바로 다시 재생하기 위한 부분이다.

이렇게 해서 비디오 재생이 완료되면 처음 위치로 이동시켜 다시 재생하는, 무한 반복 영상 재생기가 완성되었다.

3. 영상 재생해보기

전에 테스트했던 영상으로 다시 재생시켰다. 따로 멈추지 않는 한 계속해서 처음으로 돌아가며 반복 재생 될 것이고, 그때마다 콘솔창에 재생 완료 문구가 뜰 것이다.

9초 짜리 영상을 재생 시켜 끝날때마다 콘솔창에 "비디오가 끝났습니다."가 정상적으로 출력되는 것을 확인하였다.


다음 영상으로 넘어가보자

이제 단순 반복이 아니라 다음 영상으로 넘어가서 재생하는 것을 해볼 것이다. 처음 영상을 재생할 때와 마찬가지로 다음으로 재생될 예제 영상인 nextExample 이라는 영상을 Resources 폴더에 추가해준 뒤 진행하였다.

폴더 이름을 Recources라고 쓴 건 오타..

    @objc func videoDidEnd(notification: NSNotification){
        print("다음 비디오를 재생합니다.")
        if let nextURL = Bundle.main.url(forResource: "nextExample", withExtension: "MOV") {
            player?.replaceCurrentItem(with: AVPlayerItem(url: nextURL))
            player?.play()
        }
    }

videoDidEnd 메서드를 다음과 같이 변경하였다. 비디오를 처음 재생할 때와 거의 같은 방식이고, 비디오를 재생시켜주는 AVPlayer의 아이템을 replaceCurrentItem(with: )을 사용해 nextExample의 url로 대체하였다.

이렇게 하면 처음 example 영상이 처음 재생되고, 재생이 끝나면 "다음 비디오를 재생합니다" 라는 문구가 콘솔에 뜨며 다음 영상인 nextExample이 재생될 것이다.

영상 두 개가 잘 재생되는지 확인해보기

의도한대로 처음 영상이 재생이 끝나고 "다음 비디오를 재생합니다" 문구와 함께 다음 영상이 재생되는 것을 확인하였다.


뭔가 아쉬운데..

다음 영상으로 넘어가는 건 좋은데 콘솔 화면 외에 앱 내에서 별다른 액션 없이 바로 다음으로 넘어가는 건 뭔가 아쉬운 느낌이 들어서 팝업 알림을 통해 다음 영상으로 넘어갈지 말지 사용자가 정할 수 있도록 해보고 싶어졌다.

1. UIAlertController 생성하기

팝업 알림을 띄우기 위해선 UIAlertController를 사용해야 한다. UIAlertController는 iOS에서 팝업을 띄우는 기본 방법이며, 사용자가 팝업에서 선택한 옵션에 따라 동작을 제어할 수 있다.

let alert = UIAlertController(title: "비디오 종료", 
        		      message: "다음 비디오를 재생할까요?", 
			      preferredStyle: .alert)
self.present(alert, animated: true, completion: nil)

다음 영상으로 재생하기 전 팝업 알림을 띄우기 위한 UIAlertController를 생성하는 부분이다.

  • title은 팝업 알림의 제목을 정하는 부분으로 "비디오 종료" 라는 이름의 팝업 알림이 뜨도록 설정한 부분이다.
  • message는 팝업 알림의 알림 내용을 정하는 부분으로 팝업 알림 안에 "다음 비디오를 재생할까요?" 라는 문구가 나올 것이다.
  • preferredStyle은 알림의 형태를 정하는 부분으로 .alert로 설정해 팝업 형태로 띄우는 설정을 하는 부분이다.
  • self.present 부분은 알림을 현재 화면에 띄우는 부분이다. completion은 알림을 띄운 후 실행할 코드를 작성 할 수 있는데 여기선 일단 nil로 지정해 추가 동작은 하지 않도록 하였다.

이 부분은 팝업 알림의 내용을 설정하는 부분으로 알림만 띄우는 것이고 아직 사용자가 재생을 취소할지, 다음 영상을 재생할지 묻는 부분이 없다. 따라서 그냥 실행해버리면 다음과 같은 팝업 알림이 나온다.

묻기만 하고 답은 듣지 않는 팝업 알림😅

2. 확인, 취소 버튼 만들기

내가 원하는 기능은 사용자가 다음 영상을 재생할지 말지 정하는 것이므로 이 뒤에 확인/취소 버튼을 추가해 다음 영상 재생 혹은 재생 취소를 묻는 부분을 추가 할 것이다.

        let confirmAction = UIAlertAction(title: "확인", style: .default) { _ in
            print("다음 비디오를 재생합니다.")

            if let nextUrl = Bundle.main.url(forResource: "nextExample", withExtension: "MOV") {
                self.player?.replaceCurrentItem(with: AVPlayerItem(url: nextUrl))
                self.player?.play()
            }
        }
        
        let cancelAction = UIAlertAction(title: "취소", style: .cancel) { _ in
            print("비디오 재생이 취소되었습니다.")
        }
        
        alert.addAction(confirmAction)
        alert.addAction(cancelAction)

self.present 부분 전에 다음과 같은 코드를 추가하였다.

confirmAction 부분이 확인 버튼의 동작을 만드는 부분, cancleAction이 취소 버튼의 동작을 만드는 부분, addAction이 정의한 동작들을 버튼으로 추가하는 부분이다.

title은 버튼의 이름을 지정하는 것이고, style은 버튼의 스타일을 정하는 것으로, .default는 일반 버튼, .cancel은 취소 버튼으로 지정하는 것이다.

확인 버튼은 다음 영상을 재생할 수 있도록 하는 부분으로 앞부분과 거의 같고, 취소 버튼은 비디오 재생이 취소되었다는 문구가 뜨도록 하였다.

3. 잘 되는지 확인해보자

이제 첫번째 영상이 끝나면 팝업 알림이 뜨면서 다음 영상을 재생할지, 영상 재생을 취소할지 선택할 수 있을 것이다. 다만 취소 부분은 따로 영상을 끝내거나 하지 않고 그냥 콘솔에 문구만 뜨도록 하였으므로 추가 작성이 필요하다.

처음 영상이 끝난 뒤 팝업 알림이 뜨고, 취소 버튼을 누르면 영상 재생이 취소되었다는 문구가 콘솔에 뜬 뒤 아무런 동작도 하지 않고, 다시 재생해서 영상이 끝난 뒤 팝업 알림에서 확인 버튼을 누르면 다음 비디오를 재생한다는 콘솔 문구와 함께 다음 영상이 재생 되는 것을 확인할 수 있다. 의도대로 잘 작동한 것이다.


저번에 NavigationController 를 공부할 때 사용자로부터 입력값을 받는 걸 다른 화면에서 처리할 게 아니라 한 화면에서  팝업 알림을 통해 입력 값을 받아 처리하는 걸 제안 받았었다. 그런 방법도 있구나 싶어서 해보려다가 AlertController를 다뤄야 하길래 NavigationController랑 비슷한 줄 알고 하나도 어려운데 두 개를 동시에 하려면 힘들겠다 싶어서 아예 안해버렸는데, 막상 다뤄보니까 그렇게까지 어렵진 않았다.

다음엔 비디오 진행 상황 표시 기능과 품질 조절, 제스처 추가 기능을 해보려고 하는데 조금 찾아보니 오늘 한 것처럼 프레임 워크의 기능을 가져다 쓰면 되는 것 같아서 금방 할 수 있을 것 같다. 좀 다른 걸 추가해보려다 에러만 안 난다면.