Today’s Goal

  • Learn LBTA Youtube ep 17
    • Button - addTarget
    • AVPlayer control Button

LBTA Youtube ep 17

###How to Display Video Player Controls

비디오를 컨트롤할 수 있는 컨트롤뷰를 만들자.

오늘 코드들은 VideoLauncher.swift - VideoPlayerView class에서 모두 작성되었다.

controlsContainerView

이 뷰에 컨트롤하는 요소들을 subView로 추가할 것이다.

let controlsContainerView: UIView = {
        let view = UIView()
        view.backgroundColor = UIColor(white: 0, alpha: 1)
        return view
    }()

controlsContainerView를 closure로 선언 - return과 execute 잊지말기

alpha 값을 반투명하게 지정할 것이다. 실제 Youtube처럼

하지만 이번 ep까지는 완전 투명하게 사용 (play 중)

선언시에는 검은 화면이여야하기 때문에 alpha : 1

override init(frame: CGRect) {
        super.init(frame: frame)
        
        setupPlayerView()
        
        controlsContainerView.frame = frame
        addSubview(controlsContainerView)
        
        backgroundColor = .black
    }

여기서 만약 setupPlayerViewaddSubview(controlsContainerView) 아래에 선언하면 playerView위에 controlsContainerView가 올라가므로 아무것도 보이지 않고 소리만 나오게 된다.

private func setupPlayerView() {
        let urlString = "https://firebasestorage.googleapis.com/v0/b/gameofchats-762ca.appspot.com/o/message_movies%2F12323439-9729-4941-BA07-2BAE970967C7.mov?alt=media&token=3e37a093-3bc8-410f-84d3-38332af9c726"
        
        if let url = NSURL(string: urlString) {
            let player = AVPlayer(url: url as URL)
            
            let playerLayer = AVPlayerLayer(player: player)
            
            self.layer.addSublayer(playerLayer)
            playerLayer.frame = self.frame
            
            player.play()
            
            player.addObserver(self, forKeyPath: "currentItem.loadedTimeRanges", options: .new, context: nil)
        }
    }

refactor: 기존 PlayerView관련 세팅을 따로 뺀다.

Indicator

let activityIndicatorView: UIActivityIndicatorView = {
        let aiv = UIActivityIndicatorView(style: .white)
        aiv.translatesAutoresizingMaskIntoConstraints = false
        aiv.startAnimating()
        return aiv
    }()

IndicatorView를 만들어주고 startAnimating()을 잊지말자.

또한 frame이 아닌 constraint를 줄것이기 때문에 translatesAutoresizingMaskIntoConstraints = false 또한 까먹으면 안된다.

override init(frame: CGRect) {
        super.init(frame: frame)
        ...
        
        controlsContainerView.addSubview(activityIndicatorView)
        activityIndicatorView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
        activityIndicatorView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
        
        backgroundColor = .black
    }

Indicator의 centerX, Y를 현재 centerXAnchor, centerYAnchor와 동일하게 맞춰준다. 중앙으로-

constraint들은 모두 isActive = true를 잊지말자.

####AVPlayer Observer

private func setupPlayerView() {
        ...
        
        if let url = NSURL(string: urlString) {
            ...
            player.addObserver(self, forKeyPath: "currentItem.loadedTimeRanges", options: .new, context: nil)
        }
    }

player.addObserver로 옵저버를 추가해주는데, 인자는 순서대로 self, “currentItem.loadedTimeRanges”, .new, nil을 넣어준다. forKeyPath는 그냥 string이면 상관없는듯하다.

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        
        // this is when the player is ready and rendering frames
        if keyPath == "currentItem.loadedTimeRanges" {
            activityIndicatorView.stopAnimating()
            controlsContainerView.backgroundColor = .clear
        }
    }

그 뒤에 observerValue를 오버라이드하여 code 부분에

keyPath를 걸러서 처리한다.

여기서 indicatorView를 멈추고, controlsContainerView를 아예 투명하게 하여 보이지 않게 한다. .clear

(그래서 처음 controlsContainerView를 선언에서 아예 불투명하게 바꾸었다.)

if keyPath 안에서 print(change)를 break point를 잡아서 보면 좋다.

Pause-Play Button

controlsContainerView 안에 있는 Button을 만들자

lazy var pausePlayButton: UIButton = {
        let button = UIButton(type: .system)
        let image = UIImage(named: "pause")
        button.setImage(image, for: .normal)
        button.translatesAutoresizingMaskIntoConstraints = false
        button.tintColor = .white
        button.isHidden = true
        
        button.addTarget(self, action: #selector(handlePause), for: .touchUpInside)
        return button
    }()

어떤 View안에서 만들어질 요소들은 closure로 편하게 만들 수 있어서 좋다.

  • constraint사용 하니까 translatesAutoresizingMaskIntoConstraints = false`
  • ``tintColor`로 색깔 설정(이미지니까)
  • 처음에는 나올 필요 없으니 isHidden = true
  • 버튼 클릭 처리하기 위한 addTarget()
  • addTarget() 인자 self에 접근하기 위해서 lazy var로 변경
 var player: AVPlayer?
    
    private func setupPlayerView() {
        	...
        }
    }

이제 player를 다른 함수에서도 접근할 수 있게 밖에서 선언해준다.

var isPlaying = false

@objc func handlePause() {
        
        if isPlaying {
            player?.pause()
            pausePlayButton.setImage(UIImage(named: "play"), for: .normal)
        } else {
            player?.play()
            pausePlayButton.setImage(UIImage(named: "pause"), for: .normal)
        }
        
        isPlaying = !isPlaying
    }

handlePause에서는 pause, play를 한 곳에서 처리하게 되는데 이것을 판단하기 위한 flag 변수인 isPlaying 을 선언

isPlaying = !isPlaying

재밌는 표현이다.

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        
        // this is when the player is ready and rendering frames
        if keyPath == "currentItem.loadedTimeRanges" {
            ...
            isPlaying = true
        }
    }

default 값이 false이기 때문에 observer에서 true로 변경해준다. (rendering 될 때 이므로)

태그:

카테고리:

업데이트:

댓글남기기