나의 기록, 현진록

[Swift/iOS] AVPlayer에 Observer 추가하여 재생시간 탐지하기, UISlider을 이용하여 AVPlayer 영상 시점 이동하기, Control View 숨기기/보이기 본문

iOS

[Swift/iOS] AVPlayer에 Observer 추가하여 재생시간 탐지하기, UISlider을 이용하여 AVPlayer 영상 시점 이동하기, Control View 숨기기/보이기

guswlsdk 2022. 7. 19. 17:48
반응형

 

 

GitHub - dbguswls030/MyNetflix

Contribute to dbguswls030/MyNetflix development by creating an account on GitHub.

github.com

 

작성자가 복습겸 코딩한 내용을 기록하려고 글을 작성하는 것이지만, 혹시라도 이 글을 참고하는 사람이 있다면 넷플릭스를 클론코딩하였기 때문에 기본 동작은 유사하다는 전제하에 글을 읽어나가면 될 것이다.

 

 

용량 이슈로 배속 적용, 타이머 적용하여 ControlView 숨기기 기능 때문에 잘 안 보이지만!

 

1초마다 영상 재생 시간을 탐지하여 남은 시간을 나타내는 기능을 구현해보자.

 

영상이 재생되는 뷰가 실행됨과 동시에 영상이 재생될 수 있도록 할 것이다.

 

viewDidLoad()에는 play() 메소드가 포함되어 있고 그 안에는 addPeriodicTimeObserver()가 있다. 이 함수가 영상의 시간(길이)를 탐지할 수 있도록 동작할 것이다.

    func play(){
        player.play()
        self.timerNum = 0
        addPeriodicTimeObserver()
        playButton.isSelected = true
    }
    func pause(){
        player.pause()
        self.timerNum = 0
        removeTimeObserver()
        playButton.isSelected = false
    }
    func reset(){
        player.pause()
        removeTimeObserver()
        player.replaceCurrentItem(with: nil)
    }

 

 

 

1초마다 영상 길이를 탐지하는 Observer가 동작하도록 코드를 작성하고 전역변수인 timeObserverToken에 넣어준다.

func addPeriodicTimeObserver(){
        let timeScale = CMTime(seconds: 1, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
        timeObserverToken = player.addPeriodicTimeObserver(forInterval: timeScale, queue: .main) { [weak self] currentTime in
            
            let totalPlayTime = Float(CMTimeGetSeconds(self?.player.currentItem?.duration ?? CMTimeMake(value: 1, timescale: 1)))
            
            self?.timeSlider.maximumValue = totalPlayTime
            self?.updateTime(time: currentTime)
            
            let currentPlayTime = Float(CMTimeGetSeconds(currentTime))
            if currentPlayTime == totalPlayTime{
                self?.reset()
                self?.dismiss(animated: false)
            }
        }
    }
    func removeTimeObserver(){
        if let token = timeObserverToken{
            player.removeTimeObserver(token)
            timeObserverToken = nil
        }
    }

영상이 일시정지 되거나 영상 재생 화면을 닫을 때는 Observer가 필요 없기 때문에 removeTimeObserver() 메소드가 동작하도록 한다.

 

 

 

영상 돌리기 어캐하지...

 

가장 먼저 UISlider를 

timeSlider는 UISlider 속성을 가진 프로퍼티이다. 값이 변경될 때마다 그 이벤트를 탐지할 수 있도록 연결해준다.

 

func initTimeSlider(){
        timeSlider.tintColor = .red
        timeSlider.isContinuous = true
        timeSlider.minimumValue = 0
        timeSlider.value = .zero
        updateTime(time: .zero)
        timeSlider.addTarget(self, action: #selector(playbackSliderChagnedValue), for: .valueChanged)
}

updateTime은 UISlider의 값(바)를 변경, 이동시키며 UISlider 우측에 남은 시간을 나타내는 UILabel의 내용을 수정하는 기능을 한다. 아래에 더보기에 코드가 있다.

 

 

 

 

UISlider가 움직일 때 크게 3가지로 구분하여 동작을 정리해야한다.

이 때 값은 UISlider 내에 있는 Value를 말한다. 예를 들어 슬라이더 바를 움직였을 때 Value가 바뀐다.

이 동작은 참고로 슬라이더를 움직인 순간부터 손을 뗀 순간까지의 과정을 말한다.

 

1. 값이 변경되기 시작할 때 (최초로 슬라이더를 터치했을 때)

2. 값이 변경 중일 때

3. 값의 변경 작업이 종료될 때 (슬라이더에서 손을 떼어냈을 때)

 

1. 값이 변경되기 시작할 때

이 시점에서는 재생 중인 영상이 일시정지 된 상태여야 한다. 

아래의 코드를 보면 timer?.invalidate()는 화면을 재생, 일시정지, 화면 닫기, 슬라이더 등의 Control View를 숨기기/보이기 하는 데에 사용되는 코드이다.

 

2. 값이 변경 중일 때

슬라이더 우측에 있는 남은 시간을 나타내는 Label을 변경한다. convertTimeToString() 메소드는 아래 더보기에 있다.

 

3. 값의 변경이 끝났을 때

현재의 값에 해당하는 영상 시점으로 영상이 재생되어야 한다.

startTime()은 슬라이더에서 손을 떼어냈을 시점 이후이기 때문에 타이머를 다시 시작하여 일정 시간 이후 Control View를 숨기는 동작을 한다.

@objc func playbackSliderChagnedValue(timeSlider: UISlider, event: UIEvent){
        if let touchEvent = event.allTouches?.first {
               switch touchEvent.phase {
               case .began:
                   // handle drag began
                   pause()
                   timer?.invalidate()
                   
               case .moved:
                   // handle drag moved
                   //totaltime - slider.value
                   let seconds : Int64 = Int64(timeSlider.value * Float(CMTimeScale(NSEC_PER_SEC)))
                   let targetTime:CMTime = CMTimeMake(value: seconds, timescale: CMTimeScale(NSEC_PER_SEC))
                   self.remainingTimeLabel.text = convertTimeToString(time: targetTime)
               case .ended:
                   // handle drag ended
                   let seconds = Int64(timeSlider.value * Float(CMTimeScale(NSEC_PER_SEC)))
                   let seekTime = CMTimeMake(value: seconds, timescale: CMTimeScale(NSEC_PER_SEC))
                   self.player.seek(to: seekTime)
                   if player.rate == 0{
                       play()
                   }
                   startTimer()
               default:
                   break
               }
           }
    }

 

 

func updateTime(time: CMTime){
        self.remainingTimeLabel.text = convertTimeToString(time: time)
        timeSlider.value = Float(CMTimeGetSeconds(time))
}

func convertTimeToString(time: CMTime) -> String{
        let totalTime = Float(CMTimeGetSeconds(self.player.currentItem?.duration ?? CMTimeMake(value: 1, timescale: 1)))
        let currentTime = Float(CMTimeGetSeconds(time))
        print(currentTime, totalTime)
        guard !totalTime.isInfinite, !totalTime.isNaN, !currentTime.isInfinite, !currentTime.isNaN else{
            print("time is infinite or NaN")
            return String(format: "%02d:%02d", 0, 0)
        }
        //totalTime - currentTime
        let remainingTime = Int(totalTime-currentTime)
        let min = remainingTime / 60
        let seconds = remainingTime % 60
        return String(format: "%02d:%02d", min, seconds)
    }

 

 

다음은 Control View 숨기기 / 보이기 기능이다. 

 

 

 

화면에 일정 시간 동안 입력이 없을 경우 자동으로 뷰가 사라지고, 터치를 하면 뷰가 보여지도록 동작한다.

 override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        if controlView.alpha == 0{
            showControlView()
        }else if controlView.alpha == 1{
            hideControlView()
        }
    }

 

화면이 보여지고 난 이후에는 일정 시간이 지나면 다시 숨기는 동작이 가능하도록 하기 위해 타이머를 구현해야한다.

    func hideControlView(){
        UIView.animate(withDuration: 0.4){
            self.controlView.alpha = 0
        }
    }
    func showControlView(){
        UIView.animate(withDuration: 0.4){
            self.controlView.alpha = 1
        }
        startTimer()
    }

 

 

 

다음은 타이머와 관련된 코드이다. 영상이 재생되는 뷰가 실행됨과 동시에 시간을 계산할 수 있도록 viewDidLoad()에 추가하도록 하자.

 

Timer.scheduledTimer에 있는 파라미터 timeInterval에 1을 주어 1초마다 연결한 메소드가 실행될 수 있도록 한다.

 

    func startTimer(){
        timerNum = 0
        if timer != nil{
            timer?.invalidate()
        }
        timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(autoHideControlView), userInfo: nil, repeats: true)
    }

 

다음은 1초마다 실행될 메소드이다. 

timerNum이 1초마다 1씩 증가할테고, 3이 될 경우 hideControlView()가 실행된다. 슬라이더, 재생, 일시정지 버튼, 화면 닫기 버튼 등 UI를 숨기는 동작을 한다.

 

    @objc func autoHideControlView(){
        if timerNum == 3{
            hideControlView()
            timer?.invalidate()
            timer = nil
        }
        timerNum += 1
    }

이외에도 이전에서 설명한 슬라이더를 움직이는 과정에서는 타이머가 종료되어야 한다. 화면이 다시 숨겨지는 3초 이상으로 슬라이더를 동작하고 있으면 동작하고 있는 도중 ControlView가 사라지기 때문이다.

 

 

 

코드를 일부만 가져왔기 때문에 전체적인 코드를 확인하려면 글 상단에 게시된 작성자의 깃허브에서 동작시키며 확인하면 될 것 같다. 

 

 

 

 

반응형